From 1b6945c27bbc2f3b4c348942341feab670b60d3b Mon Sep 17 00:00:00 2001 From: Avi Date: Wed, 4 Apr 2018 15:54:19 -0500 Subject: [PATCH 001/495] Fix broken images and new discord invite --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9b0e212e1..f3b4a0e8c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ ![](http://i.imgur.com/qQsN78U.png) ____ -[![Discord](https://img.shields.io/discord/102860784329052160.svg)](https://discord.gg/KxYZ64w) +[![Discord](https://img.shields.io/discord/102860784329052160.svg)](https://discord.gg/Sa7wNWb) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/ombi.svg)](https://hub.docker.com/r/linuxserver/ombi/) [![Github All Releases](https://img.shields.io/github/downloads/tidusjar/Ombi/total.svg)](https://github.com/tidusjar/Ombi) [![firsttimersonly](http://img.shields.io/badge/first--timers--only-friendly-blue.svg?style=flat-square)](http://www.firsttimersonly.com/) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/ombi/localized.svg)](https://crowdin.com/project/ombi) -[![Patreon](https://www.ombi.io/img/patreondonate.svg)](https://patreon.com/tidusjar/Ombi) -[![Paypal](https://www.ombi.io/img/paypaldonate.svg)](https://paypal.me/PlexRequestsNet) +[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) +[![Paypal](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://paypal.me/PlexRequestsNet) ___ From 6b7f5e9ef4352c785711baa473374b11815d89ba Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 4 Apr 2018 22:21:59 +0100 Subject: [PATCH 002/495] Update about.component.html Updated the Discord link in the settings page --- src/Ombi/ClientApp/app/settings/about/about.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/settings/about/about.component.html b/src/Ombi/ClientApp/app/settings/about/about.component.html index 6ad185c54..578cf36f8 100644 --- a/src/Ombi/ClientApp/app/settings/about/about.component.html +++ b/src/Ombi/ClientApp/app/settings/about/about.component.html @@ -38,7 +38,7 @@ Discord - https://discord.gg/KxYZ64w + https://discord.gg/KxYZ64w From 5cc030b23704284f808b1f96e995fa0fce597952 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 4 Apr 2018 22:22:45 +0100 Subject: [PATCH 003/495] Update about.component.html !wip --- src/Ombi/ClientApp/app/settings/about/about.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/settings/about/about.component.html b/src/Ombi/ClientApp/app/settings/about/about.component.html index 578cf36f8..e05677c0f 100644 --- a/src/Ombi/ClientApp/app/settings/about/about.component.html +++ b/src/Ombi/ClientApp/app/settings/about/about.component.html @@ -38,7 +38,7 @@ Discord - https://discord.gg/KxYZ64w + https://discord.gg/ From 5693eda243c4a55f65a2895102eb8224f36718f5 Mon Sep 17 00:00:00 2001 From: Avi Date: Wed, 4 Apr 2018 16:35:44 -0500 Subject: [PATCH 004/495] Fix discord current user count --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3b4a0e8c..c4c0c4029 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![](http://i.imgur.com/qQsN78U.png) ____ -[![Discord](https://img.shields.io/discord/102860784329052160.svg)](https://discord.gg/Sa7wNWb) +[![Discord](https://img.shields.io/discord/270828201473736705.svg)](https://discord.gg/Sa7wNWb) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/ombi.svg)](https://hub.docker.com/r/linuxserver/ombi/) [![Github All Releases](https://img.shields.io/github/downloads/tidusjar/Ombi/total.svg)](https://github.com/tidusjar/Ombi) [![firsttimersonly](http://img.shields.io/badge/first--timers--only-friendly-blue.svg?style=flat-square)](http://www.firsttimersonly.com/) From 754ba3247fe7f779c96f76e8194deca3088b0ebd Mon Sep 17 00:00:00 2001 From: Louis Laureys Date: Thu, 5 Apr 2018 03:06:37 +0200 Subject: [PATCH 005/495] The fact that this button has another style really bothers me. Not anymore! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c4c0c4029..1c2dc9559 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ____ [![Discord](https://img.shields.io/discord/270828201473736705.svg)](https://discord.gg/Sa7wNWb) [![Docker Pulls](https://img.shields.io/docker/pulls/linuxserver/ombi.svg)](https://hub.docker.com/r/linuxserver/ombi/) [![Github All Releases](https://img.shields.io/github/downloads/tidusjar/Ombi/total.svg)](https://github.com/tidusjar/Ombi) -[![firsttimersonly](http://img.shields.io/badge/first--timers--only-friendly-blue.svg?style=flat-square)](http://www.firsttimersonly.com/) +[![firsttimersonly](http://img.shields.io/badge/first--timers--only-friendly-blue.svg)](http://www.firsttimersonly.com/) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/ombi/localized.svg)](https://crowdin.com/project/ombi) [![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](https://patreon.com/tidusjar/Ombi) From acb62f476882307ee8f11981e2a7e064ed5d4a58 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 6 Apr 2018 21:36:54 +0100 Subject: [PATCH 006/495] Added the ability to turn off TV or Movies from the newsletter --- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 6 +++--- .../Models/Notifications/NewsletterSettings.cs | 2 ++ .../ClientApp/app/interfaces/INotificationSettings.ts | 2 ++ .../settings/notifications/newsletter.component.html | 10 ++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index d8629e410..28db7bff2 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -229,20 +229,20 @@ namespace Ombi.Schedule.Jobs.Ombi return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, IQueryable plexEpisodes, IQueryable embyEp) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, IQueryable plexEpisodes, IQueryable embyEp, NewsletterSettings settings) { var sb = new StringBuilder(); var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie); var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie); - if (plexMovies.Any() || embyMovies.Any()) + if ((plexMovies.Any() || embyMovies.Any()) && !settings.DisableMovies) { sb.Append("

New Movies:



"); await ProcessPlexMovies(plexMovies, sb); await ProcessEmbyMovies(embyMovies, sb); } - if (plexEpisodes.Any() || embyEp.Any()) + if ((plexEpisodes.Any() || embyEp.Any()) && !settings.DisableTv) { sb.Append("

New Episodes:



"); await ProcessPlexTv(plexEpisodes, sb); diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index 380e2d743..0fed1418e 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -2,6 +2,8 @@ { public class NewsletterSettings : Settings { + public bool DisableTv { get; set; } + public bool DisableMovies { get; set; } public bool Enabled { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts index a7944f1a2..5105ef13c 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts @@ -57,6 +57,8 @@ export interface IDiscordNotifcationSettings extends INotificationSettings { export interface INewsletterNotificationSettings extends INotificationSettings { notificationTemplate: INotificationTemplates; + disableMovies: boolean; + disableTv: boolean; } export interface ITelegramNotifcationSettings extends INotificationSettings { diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html index 19ab565b6..b2c9ea179 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html @@ -9,6 +9,16 @@
+ +
+
+ +
+
+
+
+ +
From 20a9720fdd5b0d6e6e2a5303f5d4950a9dd4ac96 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 6 Apr 2018 21:51:31 +0100 Subject: [PATCH 007/495] Fixed build !wip --- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 28db7bff2..8d95d2e44 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -100,11 +100,11 @@ namespace Ombi.Schedule.Jobs.Ombi var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10); - body = await BuildHtml(plexm, embym, plext, embyt); + body = await BuildHtml(plexm, embym, plext, embyt, settings); } else { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend); + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); if (body.IsNullOrEmpty()) { return; From 3aad8bbe6bf399e48a45db7eaf14b54db49da1a6 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 6 Apr 2018 22:49:25 +0100 Subject: [PATCH 008/495] Made some improvements to the Sonarr Sync job #2127 --- src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs index 8e13d6f9e..5ee55d167 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; @@ -43,14 +45,15 @@ namespace Ombi.Schedule.Jobs.Sonarr var series = await _api.GetSeries(settings.ApiKey, settings.FullUri); if (series != null) { - var sonarrSeries = series as IList ?? series.ToList(); + var sonarrSeries = series as ImmutableHashSet ?? series.ToImmutableHashSet(); var ids = sonarrSeries.Select(x => x.tvdbId); await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrCache"); - var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToList(); + var entites = ids.Select(id => new SonarrCache { TvDbId = id }).ToImmutableHashSet(); await _ctx.SonarrCache.AddRangeAsync(entites); - + entites.Clear(); + await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); foreach (var s in sonarrSeries) { @@ -67,10 +70,10 @@ namespace Ombi.Schedule.Jobs.Sonarr TvDbId = s.tvdbId, HasFile = episode.hasFile })); + _log.LogDebug("Commiting the transaction"); + await _ctx.SaveChangesAsync(); } - _log.LogDebug("Commiting the transaction"); - await _ctx.SaveChangesAsync(); } } catch (Exception e) From 4d285b071e3364ee74443d214579b571e3294da7 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 6 Apr 2018 23:28:14 +0100 Subject: [PATCH 009/495] Update node version !wip --- appveyor.yml | 2 +- src/Ombi/package-lock.json | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c513c650e..cb4af6921 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ version: 3.0.{build} configuration: Release os: Visual Studio 2017 environment: - nodejs_version: "7.8.0" + nodejs_version: "9.9.0" install: # Get the latest stable version of Node.js or io.js diff --git a/src/Ombi/package-lock.json b/src/Ombi/package-lock.json index e3ef65167..f5e18da7b 100644 --- a/src/Ombi/package-lock.json +++ b/src/Ombi/package-lock.json @@ -12344,7 +12344,8 @@ "uglify-to-browserify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=" + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true }, "uglifyjs-webpack-plugin": { "version": "1.1.8", @@ -12945,7 +12946,7 @@ "os-locale": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "integrity": "sha1-QrwpAKa1uL0XN2yOiCtlr8zyS/I=", "requires": { "execa": "0.7.0", "lcid": "1.0.0", @@ -12987,7 +12988,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "requires": { "is-fullwidth-code-point": "2.0.0", "strip-ansi": "4.0.0" From 9428c668251c20c2f81e2085731cab796108681a Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 6 Apr 2018 23:31:05 +0100 Subject: [PATCH 010/495] Fixed node version !wip --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index cb4af6921..9d97046d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,7 +2,7 @@ version: 3.0.{build} configuration: Release os: Visual Studio 2017 environment: - nodejs_version: "9.9.0" + nodejs_version: "Current" install: # Get the latest stable version of Node.js or io.js From daaf52d771c29d056076818a32634f788972010e Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 6 Apr 2018 23:56:31 +0100 Subject: [PATCH 011/495] Memory improvements --- src/Ombi.Schedule/JobSetup.cs | 36 +++++++++++++++++++++++++++++++++-- src/Ombi/Startup.cs | 9 ++++++--- src/Ombi/StartupExtensions.cs | 4 ++++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index 85b842d27..b328e6daf 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -1,4 +1,5 @@ -using Hangfire; +using System; +using Hangfire; using Ombi.Core.Settings; using Ombi.Schedule.Jobs; using Ombi.Schedule.Jobs.Couchpotato; @@ -12,7 +13,7 @@ using Ombi.Settings.Settings.Models; namespace Ombi.Schedule { - public class JobSetup : IJobSetup + public class JobSetup : IJobSetup, IDisposable { public JobSetup(IPlexContentSync plexContentSync, IRadarrSync radarrSync, IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter, @@ -65,5 +66,36 @@ namespace Ombi.Schedule RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s)); } + + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _plexContentSync?.Dispose(); + _radarrSync?.Dispose(); + _updater?.Dispose(); + _plexUserImporter?.Dispose(); + _embyContentSync?.Dispose(); + _embyUserImporter?.Dispose(); + _sonarrSync?.Dispose(); + _cpCache?.Dispose(); + _srSync?.Dispose(); + _jobSettings?.Dispose(); + _refreshMetadata?.Dispose(); + _newsletter?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } } } diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 1d7d67550..4aaadacb6 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -11,6 +11,7 @@ using Hangfire; using Hangfire.Console; using Hangfire.Dashboard; using Hangfire.SQLite; +using Microsoft.ApplicationInsights.Extensibility; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -85,7 +86,7 @@ namespace Ombi // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { - + TelemetryConfiguration.Active.DisableTelemetry = true; // Add framework services. services.AddDbContext(); @@ -183,12 +184,12 @@ namespace Ombi Authorization = new[] { new HangfireAuthorizationFilter() } }); GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 3 }); - + // Setup the scheduler var jobSetup = app.ApplicationServices.GetService(); jobSetup.Setup(); ctx.Seed(); - + var provider = new FileExtensionContentTypeProvider { Mappings = { [".map"] = "application/octet-stream" } }; app.UseStaticFiles(new StaticFileOptions() @@ -218,6 +219,8 @@ namespace Ombi name: "spa-fallback", defaults: new { controller = "Home", action = "Index" }); }); + + ombiService.Dispose(); } } diff --git a/src/Ombi/StartupExtensions.cs b/src/Ombi/StartupExtensions.cs index e4dae18e4..1c8f54b4e 100644 --- a/src/Ombi/StartupExtensions.cs +++ b/src/Ombi/StartupExtensions.cs @@ -168,6 +168,7 @@ namespace Ombi if (user == null) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + context.Response.RegisterForDispose(um); await context.Response.WriteAsync("Invalid User Access Token"); } else @@ -177,6 +178,7 @@ namespace Ombi var roles = await um.GetRolesAsync(user); var principal = new GenericPrincipal(identity, roles.ToArray()); context.User = principal; + context.Response.RegisterForDispose(um); await next(); } } @@ -189,6 +191,7 @@ namespace Ombi if (!valid) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; + context.Response.RegisterForDispose(settingsProvider); await context.Response.WriteAsync("Invalid API Key"); } else @@ -196,6 +199,7 @@ namespace Ombi var identity = new GenericIdentity("API"); var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" }); context.User = principal; + context.Response.RegisterForDispose(settingsProvider); await next(); } } From 386f5b155091b6afb318de0443da128c30a47294 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 6 Apr 2018 23:56:57 +0100 Subject: [PATCH 012/495] Emby improvments on the way we sync/cache the data. --- .../Rule/Search/EmbyAvailabilityRuleTests.cs | 4 +- src/Ombi.Core/Engine/RecentlyAddedEngine.cs | 8 +- .../Rule/Rules/Search/EmbyAvailabilityRule.cs | 6 +- .../Jobs/Emby/EmbyAvaliabilityChecker.cs | 27 +- .../Jobs/Emby/EmbyContentSync.cs | 30 +- .../Jobs/Emby/EmbyEpisodeSync.cs | 12 +- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 25 +- .../Jobs/Ombi/RefreshMetadata.cs | 84 +- src/Ombi.Store/Entities/EmbyContent.cs | 7 + src/Ombi.Store/Entities/EmbyEpisode.cs | 6 + .../20180406224743_EmbyMetadata.Designer.cs | 948 ++++++++++++++++++ .../Migrations/20180406224743_EmbyMetadata.cs | 69 ++ .../Migrations/OmbiContextModelSnapshot.cs | 12 + .../Repository/EmbyContentRepository.cs | 50 +- .../Repository/IEmbyContentRepository.cs | 12 +- 15 files changed, 1221 insertions(+), 79 deletions(-) create mode 100644 src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.Designer.cs create mode 100644 src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.cs diff --git a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs index a7f20ac40..0633d641e 100644 --- a/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/EmbyAvailabilityRuleTests.cs @@ -25,7 +25,7 @@ namespace Ombi.Core.Tests.Rule.Search [Test] public async Task Movie_ShouldBe_Available_WhenFoundInEmby() { - ContextMock.Setup(x => x.Get(It.IsAny())).ReturnsAsync(new EmbyContent + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).ReturnsAsync(new EmbyContent { ProviderId = "123" }); @@ -39,7 +39,7 @@ namespace Ombi.Core.Tests.Rule.Search [Test] public async Task Movie_ShouldBe_NotAvailable_WhenNotFoundInEmby() { - ContextMock.Setup(x => x.Get(It.IsAny())).Returns(Task.FromResult(default(EmbyContent))); + ContextMock.Setup(x => x.GetByTheMovieDbId(It.IsAny())).Returns(Task.FromResult(default(EmbyContent))); var search = new SearchMovieViewModel(); var result = await Rule.Execute(search); diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs index 59be359f8..8782ea028 100644 --- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -152,7 +152,9 @@ namespace Ombi.Core.Engine model.Add(new RecentlyAddedMovieModel { Id = emby.Id, - ImdbId = emby.ProviderId, + ImdbId = emby.ImdbId, + TheMovieDbId = emby.TheMovieDbId, + TvDbId = emby.TvDbId, AddedAt = emby.AddedAt, Title = emby.Title, }); @@ -211,7 +213,9 @@ namespace Ombi.Core.Engine model.Add(new RecentlyAddedTvModel { Id = emby.Id, - ImdbId = emby.ProviderId, + ImdbId = emby.ImdbId, + TvDbId = emby.TvDbId, + TheMovieDbId = emby.TheMovieDbId, AddedAt = emby.AddedAt, Title = emby.Title, EpisodeNumber = episode.EpisodeNumber, diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 74b537352..8ac96701d 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -23,20 +23,20 @@ namespace Ombi.Core.Rule.Rules.Search EmbyContent item = null; if (obj.ImdbId.HasValue()) { - item = await EmbyContentRepository.Get(obj.ImdbId); + item = await EmbyContentRepository.GetByImdbId(obj.ImdbId); } if (item == null) { if (obj.TheMovieDbId.HasValue()) { - item = await EmbyContentRepository.Get(obj.TheMovieDbId); + item = await EmbyContentRepository.GetByTheMovieDbId(obj.TheMovieDbId); } if (item == null) { if (obj.TheTvDbId.HasValue()) { - item = await EmbyContentRepository.Get(obj.TheTvDbId); + item = await EmbyContentRepository.GetByTvDbId(obj.TheTvDbId); } } } diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs index af5f58cb4..59b5adc96 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyAvaliabilityChecker.cs @@ -70,7 +70,16 @@ namespace Ombi.Schedule.Jobs.Emby foreach (var movie in movies) { - var embyContent = await _repo.Get(movie.ImdbId); + EmbyContent embyContent = null; + if (movie.TheMovieDbId > 0) + { + embyContent = await _repo.GetByTheMovieDbId(movie.TheMovieDbId.ToString()); + } + else if(movie.ImdbId.HasValue()) + { + embyContent = await _repo.GetByImdbId(movie.ImdbId); + } + if (embyContent == null) { // We don't have this yet @@ -112,8 +121,20 @@ namespace Ombi.Schedule.Jobs.Emby foreach (var child in tv) { - var tvDbId = child.ParentRequest.TvDbId; - var seriesEpisodes = embyEpisodes.Where(x => x.Series.ProviderId == tvDbId.ToString()); + IQueryable seriesEpisodes; + if (child.ParentRequest.TvDbId > 0) + { + seriesEpisodes = embyEpisodes.Where(x => x.Series.TvDbId == child.ParentRequest.TvDbId.ToString()); + } + else if(child.ParentRequest.ImdbId.HasValue()) + { + seriesEpisodes = embyEpisodes.Where(x => x.Series.ImdbId == child.ParentRequest.ImdbId); + } + else + { + continue; + } + foreach (var season in child.SeasonRequests) { foreach (var episode in season.Episodes) diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs index 9d054ea7c..99d4f5a85 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs @@ -38,12 +38,21 @@ namespace Ombi.Schedule.Jobs.Emby public async Task Start() { - var embySettings = await _settings.GetSettingsAsync(); + var embySettings = await _settings.GetSettingsAsync(); if (!embySettings.Enable) return; foreach (var server in embySettings.Servers) - await StartServerCache(server); + { + try + { + await StartServerCache(server); + } + catch (Exception e) + { + _logger.LogError(e, "Exception when caching Emby for server {0}", server.Name); + } + } // Episodes BackgroundJob.Enqueue(() => _episodeSync.Start()); @@ -55,8 +64,11 @@ namespace Ombi.Schedule.Jobs.Emby if (!ValidateSettings(server)) return; + await _repo.ExecuteSql("DELETE FROM EmbyEpisode"); + await _repo.ExecuteSql("DELETE FROM EmbyContent"); + var movies = await _api.GetAllMovies(server.ApiKey, server.AdministratorId, server.FullUri); - var mediaToAdd = new List(); + var mediaToAdd = new HashSet(); foreach (var movie in movies.Items) { if (movie.Type.Equals("boxset", StringComparison.CurrentCultureIgnoreCase)) @@ -96,7 +108,9 @@ namespace Ombi.Schedule.Jobs.Emby if (existingTv == null) mediaToAdd.Add(new EmbyContent { - ProviderId = tvInfo.ProviderIds.Tvdb, + TvDbId = tvInfo.ProviderIds?.Tvdb, + ImdbId = tvInfo.ProviderIds?.Imdb, + TheMovieDbId = tvInfo.ProviderIds?.Tmdb, Title = tvInfo.Name, Type = EmbyMediaType.Series, EmbyId = tvShow.Id, @@ -110,18 +124,14 @@ namespace Ombi.Schedule.Jobs.Emby private async Task ProcessMovies(MovieInformation movieInfo, ICollection content) { - if (string.IsNullOrEmpty(movieInfo.ProviderIds.Imdb)) - { - Log.Error("Provider Id on movie {0} is null", movieInfo.Name); - return; - } // Check if it exists var existingMovie = await _repo.GetByEmbyId(movieInfo.Id); if (existingMovie == null) content.Add(new EmbyContent { - ProviderId = movieInfo.ProviderIds.Imdb, + ImdbId = movieInfo.ProviderIds.Imdb, + TheMovieDbId = movieInfo.ProviderIds?.Tmdb, Title = movieInfo.Name, Type = EmbyMediaType.Movie, EmbyId = movieInfo.Id, diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs index 749abd761..df00a37e6 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyEpisodeSync.cs @@ -85,10 +85,10 @@ namespace Ombi.Schedule.Jobs.Emby } var epInfo = await _api.GetEpisodeInformation(ep.Id, server.ApiKey, server.AdministratorId, server.FullUri); - if (epInfo?.ProviderIds?.Tvdb == null) - { - continue; - } + //if (epInfo?.ProviderIds?.Tvdb == null) + //{ + // continue; + //} // Let's make sure we have the parent request, stop those pesky forign key errors, // Damn me having data integrity @@ -109,7 +109,9 @@ namespace Ombi.Schedule.Jobs.Emby EpisodeNumber = ep.IndexNumber, SeasonNumber = ep.ParentIndexNumber, ParentId = ep.SeriesId, - ProviderId = epInfo.ProviderIds.Tvdb, + TvDbId = epInfo.ProviderIds.Tvdb, + TheMovieDbId = epInfo.ProviderIds.Tmdb, + ImdbId = epInfo.ProviderIds.Imdb, Title = ep.Name, AddedAt = DateTime.UtcNow }); diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 8d95d2e44..31138e012 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -292,14 +292,21 @@ namespace Ombi.Schedule.Jobs.Ombi var ordered = embyContent.OrderByDescending(x => x.AddedAt); foreach (var content in ordered) { - var imdbId = content.ProviderId; - var findResult = await _movieApi.Find(imdbId, ExternalSource.imdb_id); - var result = findResult.movie_results?.FirstOrDefault(); - if(result == null) + var theMovieDbId = content.TheMovieDbId; + if (!content.TheMovieDbId.HasValue()) { - continue; + var imdbId = content.ImdbId; + var findResult = await _movieApi.Find(imdbId, ExternalSource.imdb_id); + var result = findResult.movie_results?.FirstOrDefault(); + if (result == null) + { + continue; + } + + theMovieDbId = result.id.ToString(); } - var info = await _movieApi.GetMovieInformationWithExtraInfo(result.id); + + var info = await _movieApi.GetMovieInformationWithExtraInfo(int.Parse(theMovieDbId)); if (info == null) { continue; @@ -503,7 +510,11 @@ namespace Ombi.Schedule.Jobs.Ombi { try { - int.TryParse(t.ProviderId, out var tvdbId); + if (!t.TvDbId.HasValue()) + { + continue; + } + int.TryParse(t.TvDbId, out var tvdbId); var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId); if (info == null) { diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index 225efb7d3..9d073facf 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -10,7 +10,6 @@ using Ombi.Core.Settings.Models.External; using Ombi.Helpers; using Ombi.Store.Entities; using Ombi.Store.Repository; -using Ombi.Store.Repository.Requests; namespace Ombi.Schedule.Jobs.Ombi { @@ -44,6 +43,7 @@ namespace Ombi.Schedule.Jobs.Ombi if (settings.Enable) { await StartPlex(); + await StartEmby(); } } catch (Exception e) @@ -61,6 +61,12 @@ namespace Ombi.Schedule.Jobs.Ombi await StartPlexTv(); } + private async Task StartEmby() + { + await StartEmbyMovies(); + await StartEmbyTv(); + } + private async Task StartPlexTv() { var allTv = _plexRepo.GetAll().Where(x => @@ -101,6 +107,46 @@ namespace Ombi.Schedule.Jobs.Ombi await _plexRepo.SaveChangesAsync(); } + private async Task StartEmbyTv() + { + var allTv = _embyRepo.GetAll().Where(x => + x.Type == EmbyMediaType.Series && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue() || !x.TvDbId.HasValue())); + var tvCount = 0; + foreach (var show in allTv) + { + var hasImdb = show.ImdbId.HasValue(); + var hasTheMovieDb = show.TheMovieDbId.HasValue(); + var hasTvDbId = show.TvDbId.HasValue(); + + if (!hasTheMovieDb) + { + var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title); + show.TheMovieDbId = id; + } + + if (!hasImdb) + { + var id = await GetImdbId(hasTheMovieDb, hasTvDbId, show.Title, show.TheMovieDbId, show.TvDbId); + show.ImdbId = id; + _embyRepo.UpdateWithoutSave(show); + } + + if (!hasTvDbId) + { + var id = await GetTvDbId(hasTheMovieDb, hasImdb, show.TheMovieDbId, show.ImdbId, show.Title); + show.TvDbId = id; + _embyRepo.UpdateWithoutSave(show); + } + tvCount++; + if (tvCount >= 20) + { + await _embyRepo.SaveChangesAsync(); + tvCount = 0; + } + } + await _embyRepo.SaveChangesAsync(); + } + private async Task StartPlexMovies() { var allMovies = _plexRepo.GetAll().Where(x => @@ -135,7 +181,41 @@ namespace Ombi.Schedule.Jobs.Ombi await _plexRepo.SaveChangesAsync(); } - private async Task GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title) + private async Task StartEmbyMovies() + { + var allMovies = _embyRepo.GetAll().Where(x => + x.Type == EmbyMediaType.Movie && (!x.TheMovieDbId.HasValue() || !x.ImdbId.HasValue())); + int movieCount = 0; + foreach (var movie in allMovies) + { + var hasImdb = movie.ImdbId.HasValue(); + var hasTheMovieDb = movie.TheMovieDbId.HasValue(); + // Movies don't really use TheTvDb + + if (!hasImdb) + { + var imdbId = await GetImdbId(hasTheMovieDb, false, movie.Title, movie.TheMovieDbId, string.Empty); + movie.ImdbId = imdbId; + _embyRepo.UpdateWithoutSave(movie); + } + if (!hasTheMovieDb) + { + var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title); + movie.TheMovieDbId = id; + _embyRepo.UpdateWithoutSave(movie); + } + movieCount++; + if (movieCount >= 20) + { + await _embyRepo.SaveChangesAsync(); + movieCount = 0; + } + } + + await _embyRepo.SaveChangesAsync(); + } + + private async Task GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title) { _log.LogInformation("The Media item {0} does not have a TheMovieDbId, searching for TheMovieDbId", title); FindResult result = null; diff --git a/src/Ombi.Store/Entities/EmbyContent.cs b/src/Ombi.Store/Entities/EmbyContent.cs index d9d6e6983..1d3f57f13 100644 --- a/src/Ombi.Store/Entities/EmbyContent.cs +++ b/src/Ombi.Store/Entities/EmbyContent.cs @@ -36,11 +36,18 @@ namespace Ombi.Store.Entities { public string Title { get; set; } + /// + /// OBSOLETE, Cannot delete due to DB migration issues with SQLite + /// public string ProviderId { get; set; } public string EmbyId { get; set; } public EmbyMediaType Type { get; set; } public DateTime AddedAt { get; set; } + public string ImdbId { get; set; } + public string TheMovieDbId { get; set; } + public string TvDbId { get; set; } + public ICollection Episodes { get; set; } } diff --git a/src/Ombi.Store/Entities/EmbyEpisode.cs b/src/Ombi.Store/Entities/EmbyEpisode.cs index 150829240..e4e5b6a4b 100644 --- a/src/Ombi.Store/Entities/EmbyEpisode.cs +++ b/src/Ombi.Store/Entities/EmbyEpisode.cs @@ -39,8 +39,14 @@ namespace Ombi.Store.Entities public int EpisodeNumber { get; set; } public int SeasonNumber { get; set; } public string ParentId { get; set; } + /// + /// NOT USED + /// public string ProviderId { get; set; } public DateTime AddedAt { get; set; } + public string TvDbId { get; set; } + public string ImdbId { get; set; } + public string TheMovieDbId { get; set; } public EmbyContent Series { get; set; } } diff --git a/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.Designer.cs b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.Designer.cs new file mode 100644 index 000000000..644119ea0 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.Designer.cs @@ -0,0 +1,948 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180406224743_EmbyMetadata")] + partial class EmbyMetadata + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + 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.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + 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.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.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + 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.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + 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.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.cs b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.cs new file mode 100644 index 000000000..b7f98525d --- /dev/null +++ b/src/Ombi.Store/Migrations/20180406224743_EmbyMetadata.cs @@ -0,0 +1,69 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class EmbyMetadata : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "ImdbId", + table: "EmbyEpisode", + nullable: true); + + migrationBuilder.AddColumn( + name: "TheMovieDbId", + table: "EmbyEpisode", + nullable: true); + + migrationBuilder.AddColumn( + name: "TvDbId", + table: "EmbyEpisode", + nullable: true); + + migrationBuilder.AddColumn( + name: "ImdbId", + table: "EmbyContent", + nullable: true); + + migrationBuilder.AddColumn( + name: "TheMovieDbId", + table: "EmbyContent", + nullable: true); + + migrationBuilder.AddColumn( + name: "TvDbId", + table: "EmbyContent", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "ImdbId", + table: "EmbyEpisode"); + + migrationBuilder.DropColumn( + name: "TheMovieDbId", + table: "EmbyEpisode"); + + migrationBuilder.DropColumn( + name: "TvDbId", + table: "EmbyEpisode"); + + migrationBuilder.DropColumn( + name: "ImdbId", + table: "EmbyContent"); + + migrationBuilder.DropColumn( + name: "TheMovieDbId", + table: "EmbyContent"); + + migrationBuilder.DropColumn( + name: "TvDbId", + table: "EmbyContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index a24aa583a..19e14a4ca 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -185,10 +185,16 @@ namespace Ombi.Store.Migrations b.Property("EmbyId") .IsRequired(); + b.Property("ImdbId"); + b.Property("ProviderId"); + b.Property("TheMovieDbId"); + b.Property("Title"); + b.Property("TvDbId"); + b.Property("Type"); b.HasKey("Id"); @@ -207,14 +213,20 @@ namespace Ombi.Store.Migrations 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"); diff --git a/src/Ombi.Store/Repository/EmbyContentRepository.cs b/src/Ombi.Store/Repository/EmbyContentRepository.cs index 280243455..c4377f929 100644 --- a/src/Ombi.Store/Repository/EmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/EmbyContentRepository.cs @@ -35,42 +35,28 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public class EmbyContentRepository : IEmbyContentRepository + public class EmbyContentRepository : Repository, IEmbyContentRepository { - public EmbyContentRepository(IOmbiContext db) + public EmbyContentRepository(IOmbiContext db):base(db) { Db = db; } private IOmbiContext Db { get; } - public IQueryable GetAll() + + public async Task GetByImdbId(string imdbid) { - return Db.EmbyContent.AsQueryable(); + return await Db.EmbyContent.FirstOrDefaultAsync(x => x.ImdbId == imdbid); } - - public async Task AddRange(IEnumerable content) + public async Task GetByTvDbId(string tv) { - Db.EmbyContent.AddRange(content); - await Db.SaveChangesAsync(); + return await Db.EmbyContent.FirstOrDefaultAsync(x => x.TvDbId == tv); } - - public async Task ContentExists(string providerId) + public async Task GetByTheMovieDbId(string mov) { - return await Db.EmbyContent.AnyAsync(x => x.ProviderId == providerId); - } - - public async Task Add(EmbyContent content) - { - await Db.EmbyContent.AddAsync(content); - await Db.SaveChangesAsync(); - return content; - } - - public async Task Get(string providerId) - { - return await Db.EmbyContent.FirstOrDefaultAsync(x => x.ProviderId == providerId); + return await Db.EmbyContent.FirstOrDefaultAsync(x => x.TheMovieDbId == mov); } public IQueryable Get() @@ -111,23 +97,9 @@ namespace Ombi.Store.Repository await Db.SaveChangesAsync(); } - private bool _disposed; - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - - if (disposing) - { - Db?.Dispose(); - } - _disposed = true; - } - - public void Dispose() + public void UpdateWithoutSave(EmbyContent existingContent) { - Dispose(true); - GC.SuppressFinalize(this); + Db.EmbyContent.Update(existingContent); } } } \ No newline at end of file diff --git a/src/Ombi.Store/Repository/IEmbyContentRepository.cs b/src/Ombi.Store/Repository/IEmbyContentRepository.cs index 3ed8d8abd..a893e9aca 100644 --- a/src/Ombi.Store/Repository/IEmbyContentRepository.cs +++ b/src/Ombi.Store/Repository/IEmbyContentRepository.cs @@ -6,19 +6,19 @@ using Ombi.Store.Entities; namespace Ombi.Store.Repository { - public interface IEmbyContentRepository : IDisposable + public interface IEmbyContentRepository : IRepository { - Task Add(EmbyContent content); - Task AddRange(IEnumerable content); - Task ContentExists(string providerId); IQueryable Get(); - Task Get(string providerId); - IQueryable GetAll(); + Task GetByTheMovieDbId(string mov); + Task GetByTvDbId(string tv); + Task GetByImdbId(string imdbid); Task GetByEmbyId(string embyId); Task Update(EmbyContent existingContent); IQueryable GetAllEpisodes(); Task Add(EmbyEpisode content); Task GetEpisodeByEmbyId(string key); Task AddRange(IEnumerable content); + + void UpdateWithoutSave(EmbyContent existingContent); } } \ No newline at end of file From 5942645045a3469bf53aa77b45c1feb8e27974f6 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 7 Apr 2018 00:26:05 +0100 Subject: [PATCH 013/495] Added the ability to send newsletter out to users that are not in Ombi --- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 10 ++++++++ .../Notifications/NewsletterSettings.cs | 5 +++- .../app/interfaces/INotificationSettings.ts | 1 + .../notifications/newsletter.component.html | 25 +++++++++++++++++++ .../notifications/newsletter.component.ts | 21 ++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 31138e012..1a8b21fbd 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using MailKit; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Ombi.Api.TheMovieDb; @@ -120,6 +121,15 @@ namespace Ombi.Schedule.Jobs.Ombi { return; } + + foreach (var emails in settings.ExternalEmails) + { + users.Add(new OmbiUser + { + UserName = emails, + Email = emails + }); + } var emailTasks = new List(); foreach (var user in users) { diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index 0fed1418e..f043cd74c 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -1,9 +1,12 @@ -namespace Ombi.Settings.Settings.Models.Notifications +using System.Collections.Generic; + +namespace Ombi.Settings.Settings.Models.Notifications { public class NewsletterSettings : Settings { public bool DisableTv { get; set; } public bool DisableMovies { get; set; } public bool Enabled { get; set; } + public List ExternalEmails { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts index 5105ef13c..da1e147da 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts @@ -59,6 +59,7 @@ export interface INewsletterNotificationSettings extends INotificationSettings { notificationTemplate: INotificationTemplates; disableMovies: boolean; disableTv: boolean; + externalEmails: string[]; } export interface ITelegramNotifcationSettings extends INotificationSettings { diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html index b2c9ea179..0a7c27b64 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.html @@ -53,6 +53,31 @@
When testing, the test newsletter will go to all users that have the Admin role, please ensure that there are valid email addresses for this. The test will also only grab the latest 10 movies and 10 shows just to give you an example. +
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ {{email}} +
+
+ +
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts index 460c54955..fda23208f 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts @@ -11,6 +11,7 @@ export class NewsletterComponent implements OnInit { public NotificationType = NotificationType; public settings: INewsletterNotificationSettings; + public emailToAdd: string; constructor(private settingsService: SettingsService, private notificationService: NotificationService, @@ -48,4 +49,24 @@ export class NewsletterComponent implements OnInit { }); } + public addEmail() { + + if(this.emailToAdd) { + const emailRegex = "[a-zA-Z0-9.-_]{1,}@[a-zA-Z.-]{2,}[.]{1}[a-zA-Z]{2,}"; + const match = this.emailToAdd.match(emailRegex)!; + if(match && match.length > 0) { + this.settings.externalEmails.push(this.emailToAdd); + this.emailToAdd = ""; + } else{ + this.notificationService.error("Please enter a valid email address") + } + } + } + + public deleteEmail(email: string) { + var index = this.settings.externalEmails.indexOf(email); // <-- Not supported in Date: Sat, 7 Apr 2018 00:36:28 +0100 Subject: [PATCH 014/495] !wip changelog --- CHANGELOG.md | 3665 +------------------------------------------------- 1 file changed, 55 insertions(+), 3610 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea279eae7..63cc385a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,60 @@ # Changelog +## (unreleased) + +### **New Features** + +- Added the ability to send newsletter out to users that are not in Ombi. [Jamie] + +- Added the ability to turn off TV or Movies from the newsletter. [Jamie] + +- Update about.component.html. [Jamie] + +- Update about.component.html. [Jamie] + +- Added random versioning prefix to the translations so the users don't have to clear the cache. [Jamie] + +- Added more information to the about page. [Jamie] + +- Changed let to const to adhere to linting. [Anojh] + +- Update _Layout.cshtml. [goldenpipes] + +- Update _Layout.cshtml. [goldenpipes] + +- Changed the TV Request API. We now only require the TvDbId and the seasons and episodes that you want to request. This should make integration regarding TV a lot easier. [Jamie] + +### **Fixes** + +- Emby improvments on the way we sync/cache the data. [Jamie] + +- Memory improvements. [Jamie] + +- Made some improvements to the Sonarr Sync job #2127. [Jamie] + +- Fixed #2109. [Jamie] + +- Fixed #2101. [Jamie] + +- Fixed #2105. [Jamie] + +- Fixed some styling on the issues detail page. [Jamie] + +- Fixed #2116. [Jamie] + +- Limit the amount of FileSystemWatchers being spawned. [Jamie] + +- Fixed the issue where Emby connect users could not log in #2115. [Jamie] + +- Had to update some base styles since currently some styling does not look right... [Anojh] + +- Adding wrappers and classes for LC and toggling active style for UI elements. [Anojh] + +- Fixed a little bug in the newsletter. [Jamie] + +- Fixed the issue where movies were not appearing in the newsletter for users with Emby #2111. [Jamie] + + ## v3.0.3111 (2018-03-27) ### **New Features** @@ -732,3613 +787,3 @@ - Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] -- Fix non-admin rights (#1820) [Rob Gkemeijer] - -- Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] - -- Add the Issue Reporting functionality (#1811) [Jamie] - -- Removed the forum. [tidusjar] - -- #1659 Made the option to ignore notifcations for auto approve. [Jamie] - -- New Crowdin translations (#1806) [Jamie] - -- Fixed a launch issue. [Jamie] - -- Allow users to login without a password. [Jamie] - -- Fixed the emby notifications not being sent. [Jamie] - -- #1802 and other small fixes. [tidusjar] - -- So... This sickrage thing should work now. [tidusjar] - -- Fixed emby connect login issue. [tidusjar] - -- Stop making unnecessary calls to the update service. [Jamie] - -- Fixed a bug where it blocked users with 0 limits. [Jamie] - -- Done #1788. [tidusjar] - -- More logging. [Jamie] - -- Fixed #1738. [Jamie] - -- Fixed build. [Jamie] - -- Fixed the issue where notifications were not sendind unless we restarted #1732. [tidusjar] - -- Fixed an issue with a trailing space in the subdir. [tidusjar] - -- Fixed #1774. [Jamie] - -- #1773. [Jamie] - -- Roll back rxjs (#1778) [bazhip] - -- Fixed build. [Jamie] - -- Fixed #1763. [Jamie] - -- Fix "content length error" on preview gif (#1768) [OoGuru] - -- New preview gif for Ombi V3 README (#1767) [OoGuru] - -- Remove debug code. [tidusjar] - -- Fix #1762. [tidusjar] - -- Fixed the preset themes not loading. [tidusjar] - -- Fixed #1760 and improvements on the auto updater. We may now support windows services... #1460. [Jamie] - -- Fixed #1754. [Jamie] - -- Hide the subject when it's not being used. [Jamie] - -- Error handling #1749. [Jamie] - -- New Crowdin translations (#1741) [Jamie] - -- #1732 #1722 #1711. [Jamie] - -- Fixed an issue with switching the preset themes. [Jamie] - -- Fixed #1743. [Jamie] - -- Fixed #1742. [tidusjar] - -- Fix #1742. [tidusjar] - -- Fixed landing page. [Jamie] - -- Fixed. [Jamie] - -- Translated the Requests page and fixed #1740. [Jamie] - -- Fix crash. [Jamie] - -- Sickrage done. Ish... So i've written all the code by looking at the API. the key there is i've looked at the api. I have not tested anything so expect this to fail. [Jamie] - -- SickRage settings UI. [Jamie] - -- Fixed #1721. [tidusjar] - -- Fixed the preset themes issue. [tidusjar] - -- New Crowdin translations (#1654) [Jamie] - -- Fix build. [Jamie] - -- #1460. [Jamie] - -- Fixed tests. [Jamie] - -- Return css as MIME text/css. [Jamie] - -- More added for the preset themes. [Jamie] - -- Moved around the custom styles. [Jamie] - -- More renames. [Jamie] - -- Renames. [Jamie] - -- Load the first 100 requests. [Jamie] - -- Reduce the memory consumption #1720. [Jamie] - -- Moved the schedules jobs into it's own database, see if it helps with the db locking #1720. [Jamie] - -- Fixed #1712. [tidusjar] - -- Potential fix for #1702. [tidusjar] - -- Fixed #1708. [tidusjar] - -- Fixed #1677. [tidusjar] - -- Fixed build. [tidusjar] - -- Potential fix for the DB locking issue #1720. [tidusjar] - -- #1698. [Jamie] - -- Fixed #1705. [tidusjar] - -- Fixed #1703. [tidusjar] - -- Finished adding preset themes. [Jamie] - -- Fixed #17000. [Jamie] - -- Remove the themes because waiting for a merge from lerams project. [Jamie] - -- Finsihed adding preset themes. [Jamie] - -- Fixed #1677. [Jamie] - -- Temp fix for #1683. [Jamie] - -- Fixed #1685. [Jamie] - -- Lossless Compression of images saves 83 KB (#1676) [Fish2] - -- Fixed the availability checker. [tidusjar] - -- Fixed build. [tidusjar] - -- Push out missing migration. [tidusjar] - -- Potential fix for #1674. [tidusjar] - -- Fixed an issue with the caching. [tidusjar] - -- Fixed telegram #1667. [tidusjar] - -- Fixed #1663. [tidusjar] - -- Should fix #1663. [tidusjar] - -- Stop logged in users going to the login page. [Jamie] - -- Fixed it not updating. Styles should be good now. [Jamie] - -- Re did some of the styling on the movie search page, let me know your thoughts. [Jamie] - -- Fixed #1657. [Jamie] - -- Fixed #1655. [Jamie] - -- Removed authentication resul. [Jamie] - -- New Crowdin translations (#1651) [Jamie] - -- New Crowdin translations (#1648) [Jamie] - -- New Crowdin translations (#1638) [Jamie] - -- Fixed #1644. [Jamie] - -- Moar logs #1643. [tidusjar] - -- Fixed #1640. [tidusjar] - -- Fixed the null ref exception #1460. [tidusjar] - -- Fixed landing page. [TidusJar] - -- Fixed #1641. [TidusJar] - -- Fixed #1641. [TidusJar] - -- New Crowdin translations (#1635) [Jamie] - -- Fixed #1631 and improved translation support Included startup args for the auto updater #1460 Mark TV requests as available #1632. [tidusjar] - -- Remove 32bit. [Jamie] - -- More 32bit support. [Jamie] - -- We now show "Available" for tv shows that is fully available #1602. [tidusjar] - -- Fixed the issue where we have got an episode but not the related series. #1620. [tidusjar] - -- Fixed the dropdown not working on iOS in the settings #1615. [tidusjar] - -- Fixed sonarr not monitoring the latest season #1534. [tidusjar] - -- Fixed the issue with firefox #1544. [tidusjar] - -- Fixed discord #1623. [tidusjar] - -- Add browserstack thanks (#1627) [Matt Jeanes] - -- Fix the exception #1613. [Jamie] - -- Found where we potentially are setting a new poster path, looks like the entity was being modified and being set as Tracked by entity framework, so the next time we called SaveChangesAsync() it would save the new posterpath on the entity. [Jamie] - -- Small modifications. [Jamie] - -- Fixed #1622. [Jamie] - -- Various improvements to webpack/gulp/vscode support (#1617) [Matt Jeanes] - -- Episodes in requests are now in order #1597 (#1614) [masterhuck] - -- Fixed a null reference issue in the Plex Content Cacher. [Jamie.Rees] - -- Fixed #1610. [tidusjar] - -- Really fixed the build this time. [tidusjar] - -- Fixed build. [tidusjar] - -- Made the updater work again #1460. [tidusjar] - -- Adding logging into the auto updater and also added more logging around the create inital user for #1604. [tidusjar] - -- Fixed the issue where we did not check if they are already in sonarr when choosing certain options #1540. [tidusjar] - -- We can now delete tv child requests and the parent will get remove #1603. [tidusjar] - -- Finished the api changes requested #1601. [tidusjar] - -- Fixed the Hangfire server timeout issue #1605. [tidusjar] - -- Fixed notifications not sending #1594. [tidusjar] - -- Fixed #1583 you can now delete users. Fixed the issue where the requested by was not showing. Finally fixed the broken poster paths. [tidusjar] - -- Fixed the issue where movie requests were no longer being requested. [tidusjar] - -- Started adding some more unit tests #1596. [Jamie.Rees] - -- #1588 When we make changes to any requests that we can trigger a notification, always send it to all notification agents, even if the user wont recieve it. [Jamie.Rees] - -- Add a message when email notifications are not setup when requesting a password reset. #1590. [Jamie.Rees] - -- Removed text that we no longer need. [Jamie.Rees] - -- Fixed #1574. [Jamie.Rees] - -- #1460 looks like the permissions issue has been resolved. Just need to make sure the Ombi process is terminated. [Jamie.Rees] - -- Put back the old download code. [Jamie.Rees] - -- Test. [Jamie] - -- Build sln. [Jamie.Rees] - -- Order by the username #1581. [Jamie.Rees] - -- Remove sonarr episodes from the cache table. [Jamie.Rees] - -- Couchpotato finished. [tidusjar] - -- Disable run import button if no import options are selected. [tidusjar] - -- Fixed #1574. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixes the issue with non windows systems unable to unzip the tarball #1460. [tidusjar] - -- Finished the couchpotato settings. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed #1570 #1571. [tidusjar] - -- Fixed #1547. [tidusjar] - -- Should fix #1538. [tidusjar] - -- Fixed #1553. [tidusjar] - -- Fixed #1546. [tidusjar] - -- Fixed #1543. [tidusjar] - -- Fixes an issue with Movie caching not working on develop branch of Radarr (#1567) [Jeffrey Peters] - -- This adds two fields to the Email Notifications settings page. It allows for the disabling of TLS/SSL as well as the ability to disable certificate validation when sending notification emails. (#1552) [Jeffrey Peters] - -- Fixed typo (#1551) [Codehhh] - -- Use Sqlite storage for Hangfire. [tidusjar] - -- Fixed the overrides #1539 also display it on screen now too. [tidusjar] - -- Fixed #1542 also added VSCode support. [tidusjar] - -- Fixed some cosmetic issues #865. [Jamie.Rees] - -- Fixed #1531. [Jamie.Rees] - -- Small fixes #865. [Jamie.Rees] - -- Some errors fixed and some ui improvements #865. [tidusjar] - -- Auto-scale large images down to container size (#1529) [Avi] - -- Fix logo on login page. (#1528) [Avi] - -- Another potential issue? :/ [tidusjar] - -- Real fix. [tidusjar] - -- #1513 Added storage path. [Jamie.Rees] - -- Fixed the discord issue relating to images #1513. [Jamie.Rees] - -- Fixed the issue sending movies to Radarr #1513 Fixed typo #1524. [Jamie.Rees] - -- Fixed logo on reset password pages fixed the run importer button on the user management settings. [Jamie.Rees] - -- Fixed crash/error #865. [tidusjar] - -- #1513 fixed the landing page and also the reverse proxy images. [tidusjar] - -- #1513 correctly set the child requests as approved. [tidusjar] - -- Fixed an issue that potentially causes as issue when siging into plex #865. [tidusjar] - -- Remove dev branch. [PotatoQuality] - -- Prepare readme for upcoming beta. [PotatoQuality] - -- #1513 partially fixed a bug. [tidusjar] - -- Fixed the exception. [tidusjar] - -- Fixed the application url not saving #1513. [tidusjar] - -- Fixed liniting. [tidusjar] - -- REVERSE PROXY BITCH! #1513. [tidusjar] - -- Fixed a bug where we were marking the wrong episodes as available #1513 #865. [Jamie.Rees] - -- Fixed an issue where we messed up the pages and routing. [Jamie.Rees] - -- Emby user importer is now therer! #1456. [tidusjar] - -- #1513 Added the update available icon. [tidusjar] - -- Fixed the issue of it showing as not requested when we find it in Radarr. Made the tv shows match a bit more to the movie requests Added the ability for plex and emby users to login Improved the welcome email, will only show for users that have not logged in Fixed discord notifications the about screen now checks if there is an update ready #1513. [tidusjar] - -- Support email addresses as usernames #1513. [Jamie.Rees] - -- Link to issue treath. [PotatoQuality] - -- Give correct feedback when testing email notifications #1513. [Jamie.Rees] - -- Report issue removed and the deny dropdown removed #1513. [Jamie.Rees] - -- #1513 removed the discord text when testing pushbullet. [Jamie.Rees] - -- Made a lot of changes around the notifcations to support the custom app name also started on the welcome email ##1456. [Jamie.Rees] - -- Fixed the bug where we were displaying shows where we do not have enough information to request #1513. [Jamie.Rees] - -- #1513 added the network to tv shows. [Jamie.Rees] - -- Fixed the whitespace issue #1513. [Jamie.Rees] - -- Fixed the swagger endpoint #865 #1513 Fixed the custom image issue on the login page Fixed the bug when clicking on the tab on the requests page it would switch to the wrong one Swagger is now back @ /swagger. [tidusjar] - -- Optimized images, Update old compressed image with a new lossless one. (#1514) [camjac251] - -- #1513 #865 Fixed the issue where we do not send the requests to Radarr/Sonarr when approving. [tidusjar] - -- #1506 #865 Fixed an issue with the test buttons not working correctly. [tidusjar] - -- #865 Added donation link. [tidusjar] - -- Fixed a bunch of issues on #1513. [tidusjar] - -- #1460 Added the Updater, it all seems to be working correctly. #865. [Jamie.Rees] - -- Removed percentage. [Jamie.Rees] - -- Fixed linter. [Jamie.Rees] - -- Fixed some bugs in the UI #865. [Jamie.Rees] - -- Improved the search buttons #865. [Jamie.Rees] - -- More logging #865. [Jamie.Rees] - -- Made build faster. [Jamie.Rees] - -- More logging. [Jamie.Rees] - -- Set debug level to Debug for now. [Jamie.Rees] - -- Add linting and indexes for interfaces/services (#1510) [Matt Jeanes] - -- Fixed the issue with the tv search not working #1463. [Jamie.Rees] - -- Latest practices... also probably broke some styles - sorry (#1508) [Matt Jeanes] - -- Build with the branch version. [tidusjar] - -- Build fix. [tidusjar] - -- Fixed build. [tidusjar] - -- Omgwtf so many changes. #865. [tidusjar] - -- Tests. [Jamie.Rees] - -- #1456 Started on the User Importer Also added the remember me button. [Jamie.Rees] - -- Made some UI changes, reworked the Emby and Plex screens to make them more user friendly and no so fugly. #865 Also made the login page placeholder text slightly lighter. [Jamie.Rees] - -- Cake skip verification build stuff #865. [Jamie.Rees] - -- Some fixes around the UI and managing requests #865. [tidusjar] - -- #1486. [Jamie.Rees] - -- #1486. [Jamie.Rees] - -- Upgraded to .net core 2.0 #1486. [Jamie.Rees] - -- #865 Finished the landing page, we now check the server's status. [Jamie.Rees] - -- Fixed build. [TidusJar] - -- Removed the telegram api. [Jamie.Rees] - -- Small changes on the updater #1460 #865. [Jamie.Rees] - -- Remove unused functions. [Dhruv Bhavsar] - -- Make Episode picker similar to Requests Child view. #1457 #1463. [Dhruv Bhavsar] - -- Fix merge conflict for TvRequests component. [Dhruv Bhavsar] - -- Upstream Changes... [Dhruv Bhavsar] - -- Clean up Requests page code by moving children request to old component, remove additional REST calls when merging and update component names to make more sense. [Dhruv Bhavsar] - -- Lots of different UI enhancements and fixes #865. [tidusjar] - -- Gitchangelog. [tidusjar] - -- Fixed the issue where we were using the wrong availability options. [tidusjar] - -- Fixed a bunch of bugs in Ombi #865. [tidusjar] - -- Build versioning. [Jamie.Rees] - -- #1460 The assembly versioning seems to work correctly now. [Jamie.Rees] - -- More build versioning changes #865. [tidusjar] - -- Fixed cake script. [Jamie.Rees] - -- WIP on the build versioning for the Updater #1460 #865. [Jamie.Rees] - -- Versioning. [Jamie.Rees] - -- Package versions. [Jamie.Rees] - -- #1460 #865 working on the auto updater. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Small changes around the roles #865. [tidusjar] - -- Improvements to the UI and also finished the availability checker #865 #1464. [Jamie.Rees] - -- Availability Checker #1464 #865. [Jamie.Rees] - -- Fixed ##1492 and finished the episode searcher for #1464. [Jamie.Rees] - -- #1464. [tidusjar] - -- Reload the settings #1464 #865. [Jamie.Rees] - -- #1464 added the Plex episode cacher #865. [Jamie.Rees] - -- Fixed some issues around the tv requests area Added mattermost and telegram notifications #1459 #865 #1457. [tidusjar] - -- Fix global.json. [Dhruv Bhavsar] - -- Working UI for Requests. Approval/Deny does not work as it doesn't in your code either. [Dhruv Bhavsar] - -- Enable diagnostic on build #865. [Jamie.Rees] - -- Fixed the user token issue #865. [Jamie.Rees] - -- Some small refresh token work #865. [Jamie.Rees] - -- Initial TV Requests UI rebuild. [Dhruv Bhavsar] - -- Made a start on supporting multiple emby servers, the UI needs rework #865. [Jamie.Rees] - -- #865 #1459 Added the Sender From field for email notifcations. We can now have "Friendly Names" for email notifications. [Jamie.Rees] - -- Redirect to the landing page when enabled #1458 #865. [Jamie.Rees] - -- Removed IdentityServer, it was overkill #865. [Jamie.Rees] - -- Fixed another bug with identity. #865 I'm thinking about removing it. Causing more hassle than it's worth. [tidusjar] - -- #1460 #865. [tidusjar] - -- Delete appveyor_old.yml. [Jamie] - -- Fixed path. [Jamie.Rees] - -- Silent build level. [Jamie.Rees] - -- #1459 Forgot to get the Pushbullet agent to look up the pusbullet templates rather than the Discord ones. Updated the Gitchange log. [Jamie.Rees] - -- Made the placeholder color on the login page a bit lighter #865. [Jamie.Rees] - -- Landing and login page changes #865 #1485. [tidusjar] - -- #1458 #865 More work on landing. [Jamie.Rees] - -- Working on the landing page #1458 #865. [tidusjar] - -- A lot of clean up and added a new Image api #865. [Jamie.Rees] - -- Cleaned up the Logging API slightly #1465 #865. [Jamie.Rees] - -- Fixed the Identity Server discovery bug #1456 #865. [tidusjar] - -- Fixed the issue with the Identity Server running on a different port, we can now use -url #865. [Jamie.Rees] - -- Try again. [TidusJar] - -- Publish ubuntu 16.04. [Jamie.Rees] - -- Chnaged the updater job from Minutely to Hourly. [Jamie.Rees] - -- Some work around the Auto Updater and other small changes #1460 #865. [Jamie.Rees] - -- Missed a file. [tidusjar] - -- Fixed the swagger issue. [tidusjar] - -- RDP issues. [tidusjar] - -- Appveyor build rdp investigation. [tidusjar] - -- Working on the requests page #1457 #865. [tidusjar] - -- Made the password reset email style the same as other email notifications #1456 #865. [Jamie.Rees] - -- Fixed some bugs around the authentication #1456 #865. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Fixed build #1456. [Jamie.Rees] - -- #1456 #865 Started on allowing Plex Users to sign in through the new authentication server. [Jamie.Rees] - -- Removed covalent. [Jamie.Rees] - -- #1456 Reset Password stuff #865. [Jamie.Rees] - -- Finished implimenting Identity with IdentityServer4. #865 #1456. [Jamie.Rees] - -- Moved over to using Identity Server with Asp.Net Core Identity #1456 #865. [Jamie.Rees] - -- Started on the requests rework #865. [Jamie.Rees] - -- Extended the Emby API. [Jamie.Rees] - -- Started reworking the usermanagement page #1456 #865. [tidusjar] - -- Lots of refactoring #865. [Jamie.Rees] - -- Created an individual user api endpoint so we can make the user management pages better #865. [TidusJar] - -- Lot's of refactoring. [Jamie.Rees] - -- #1462 #865 Had to refactor how we use notificaitons. So we now have more notification fields about the request. [Jamie.Rees] - -- Looks like Sonarr is finished and works. A lot simplier this time around. #865. [tidusjar] - -- More work on the Sonarr Api Integration #865. [tidusjar] - -- Started on sonarr #865. [tidusjar] - -- Small changes #865. [tidusjar] - -- Damn son. So many changes... Fixed alot of stuff around tv episodes with the new DB model #865. [tidusjar] - -- Fixed the TV Requests issue #865. [Jamie.Rees] - -- Fixed a load of bugs need to figure out what is wrong with tv requests #865. [tidusjar] - -- #865 rework the backend data. Actually use real models rather than a JSON store. [Jamie.Rees] - -- Fixed the build issue #865. [tidusjar] - -- Allow us to use Emby as a media server. [tidusjar] - -- More Update #865. [Jamie.Rees] - -- Deployment changes. [Jamie.Rees] - -- More work on the Updater. [Jamie.Rees] - -- Lots of fixes. Becoming more stable now. #865. [tidusjar] - -- Small fixes around the searching. [Jamie.Rees] - -- Some rules #865. [Jamie.Rees] - -- Oops. [TidusJar] - -- Started on the Discord API settings page. [TidusJar] - -- Email Notifications are now fully customizable and work! #865. [Jamie.Rees] - -- Small changes and fixed some stylingon the plex page #865. [Jamie.Rees] - -- More on #865 TODO, Find out whats going on with the notifications and why exceptions are being thrown. [Jamie.Rees] - -- Oops. [Jamie.Rees] - -- Ok #865 fixed the published exe. [Jamie.Rees] - -- Fixed errors. [Jamie.Rees] - -- Fixed build script. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Some more #865. [Jamie.Rees] - -- Create appveyor.yml. [Jamie] - -- The Approving child requests now work! [Jamie.Rees] - -- Fixed many bugs #865. [Jamie.Rees] - -- Loads of changes, improved the movie search stylings is back. [Jamie.Rees] - -- Moved to webpack and started on new style. [Jamie.Rees] - -- Fixed the TV search via Trakt not returning Images anymore. #865. [Jamie.Rees] - -- Rules changes and rework. [Jamie.Rees] - -- Request Grid test. [Jamie.Rees] - -- Small cleanup #865. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Started the Radarr Settings #865. [Jamie.Rees] - -- Massive amount of rework on the plex settings page. It's pretty decent now! #865. [tidusjar] - -- Fixed build. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Rules #865. [tidusjar] - -- Stuff. [Jamie.Rees] - -- Forgot to uncomment. [Jamie.Rees] - -- Tetsd. [Jamie.Rees] - -- Build task changes. [Jamie.Rees] - -- Adsa. [Jamie.Rees] - -- Appveyor. [Jamie.Rees] - -- Stuff around tokens and also builds. [Jamie.Rees] - -- Finished the Plex Content Cacher. Need to do the episodes part but things are now showing as available! #865. [tidusjar] - -- Small user changes #865. [Jamie.Rees] - -- Stuff #865 need to work on the claims correctly. [Jamie.Rees] - -- Reworked the TV model AGAIN #865. [Jamie.Rees] - -- The move! [Jamie.Rees] - -- Fixed build #865. [Jamie.Rees] - -- Fixed the user management #865. [Jamie.Rees] - -- #865 Added support for multiple plex servers. [Jamie.Rees] - -- Bleh. [tidusjar] - -- Small changes. [Jamie.Rees] - -- Fixed the build. [Jamie.Rees] - -- Fixes. [Jamie.Rees] - -- Bundling changes. [Jamie.Rees] - -- Some series information stuff, changes the pace theme too. [Jamie.Rees] - -- Docker support and more, redesign the episodes. [tidusjar] - -- Stuff around episode/season searching/requesting. [Jamie.Rees] - -- Removed redundant folders. [tidusjar] - -- Lots of backend work. [tidusjar] - -- Fixed build. [Jamie.Rees] - -- TV Request stuff. [Jamie.Rees] - -- Work around the user management. [tidusjar] - -- More. [Jamie.Rees] - -- Lots and Lots of work. [Jamie.Rees] - -- Diagnostic changes. [tidusjar] - -- Fixed hangfire exception. [tidusjar] - -- Remove xunit. [tidusjar] - -- Lots more work :( [Jamie.Rees] - -- More changes. [tidusjar] - -- #865. [Jamie.Rees] - -- Small changes. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- More mapping. [Jamie.Rees] - -- Mapping mainly. [Jamie.Rees] - -- Fix systemjs config not being included. [Matt Jeanes] - -- Fixed bundling and various improvements. [Matt Jeanes] - -- Finished the emby wizard #865. [tidusjar] - -- Finished the wizard #865 (For Plex Anyway) [tidusjar] - -- Small changes. [tidusjar] - -- More work on Wizard and Plex API #865. [tidusjar] - -- Settings. [Jamie.Rees] - -- Settings for Ombi. [Jamie.Rees] - -- Fixed some issues around the identity. [Jamie.Rees] - -- #865 more for the authentication. [tidusjar] - -- Auth. [Jamie.Rees] - -- More on the search and requests page. It's almost there for movies. Need to add some filtering logic #865. [tidusjar] - -- #865. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Messing around with the settings. [tidusjar] - -- Fixed the yml. [Jamie.Rees] - -- Remove unneeded bundle config. [Matt Jeanes] - -- Redo dotnet publish targets. [Jamie.Rees] - -- Bundling changes. [Jamie.Rees] - -- Stuff. [Jamie.Rees] - -- Move app into wwwroot. [Jamie.Rees] - -- Put uglify back in! [Jamie.Rees] - -- Wrong line. [Jamie.Rees] - -- Matt is helping. [Jamie.Rees] - -- Revert. [tidusjar] - -- Small tweaks. [tidusjar] - -- Upgrade to .Net Standard 1.6. [tidusjar] - - -## v2.2.1 (2017-04-09) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the forums. [tidusjar] - -- Updates. [tidusjar] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added a retry policy around the emby newsletter. [Jamie.Rees] - -### **Fixes** - -- Revert "Merge branch 'DotNetCore' into dev" [tidusjar] - -- More borken build. [Jamie.Rees] - -- Started adding requesting. [Jamie.Rees] - -- Done the movie searching. [tidusjar] - -- #865. [tidusjar] - -- More. [tidusjar] - -- Moar. [tidusjar] - -- Small changes. [tidusjar] - -- Styling. [Jamie.Rees] - -- MOre changes. [Jamie.Rees] - -- Spacing. [Jamie.Rees] - -- Try again. [Jamie.Rees] - -- More. [Jamie.Rees] - -- Again. [Jamie.Rees] - -- Anbother. [Jamie.Rees] - -- Another. [Jamie.Rees] - -- Another. [Jamie.Rees] - -- Retry. [Jamie.Rees] - -- A. [Jamie.Rees] - -- Fixed. [Jamie.Rees] - -- Cahnge 2. [Jamie.Rees] - -- Appveyor change. [Jamie.Rees] - -- The start of a new world. [Jamie.Rees] - -- Fixed the migration number and order by the added date for the newsletter #1264. [tidusjar] - -- Forgot this change. [tidusjar] - -- Also fixed the issue for the Emby Newsletter where episodes were not getting added :( [tidusjar] - -- #1264 "They may take our lives, but they'll never take our freedom!" [tidusjar] - -- Finished reworking the Sonarr Integration. Seems to be working as expected, faster and most stable. It's Not A Toomah! [tidusjar] - -- Small bit of work. [Jamie.Rees] - -- Made a start on the new Sonarr integration. [tidusjar] - -- For test emails, if there is no new content then just grab some old data. [tidusjar] - -- Fixed an issue where the emby newsletter was always showing series. [tidusjar] - - -## v2.2.0 (2017-03-30) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added a new setting for the Netflix option, we can now disable it appearing in the search. [tidusjar] - -- Update German Translation. [Marius Schiffer] - -- Added a release notes page, you can access via Admin>Updates>Recent Changes tab. Note to self, need to put better comments in for users to understand! [Jamie.Rees] - -- Added gravitar image. [Jamie.Rees] - -- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] - -- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] - -- Added some logging around API calls. [smcpeck] - -- Changed IEmbyAvailabilityChecker to use IEnumberables + checking actor search against Emby content + PR feedback. [smcpeck] - -- Changed actor searching to support non-actors too. [smcpeck] - -- Added a 10 second timer to refresh some new caching I put in. [smcpeck] - -- Added root folder and approving quality profiles in radarr #1065. [tidusjar] - -- Added some debugging code around the newsletter for Emby #1116. [tidusjar] - -- Added a TMDB Rate limiter for the newsletter. [tidusjar] - -- Added port check in wizard. also fixed favicon. [tidusjar] - -- Update Radarr placeholder. [d2dyno] - -- Added the user login for emby users #435. [tidusjar] - -- Added User Management support for Emby #435. [tidusjar] - -- Added emby to the sidebar #435. [tidusjar] - -- Added API endpoint for /actor/new/ to support searching for movies not already available/requested. [smcpeck] - -- Update ISSUE_TEMPLATE.md. [Jamie] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -### **Fixes** - -- Translation changes. [Jamie.Rees] - -- Syntax error. [tidusjar] - -- Fixed an issue where we were retrying the API call when the Plex users login creds were invalid. #1217. [tidusjar] - -- Slightly increased the wait time for the emby newsletter also fixed a potential error in the plex user checker. [Jamie.Rees] - -- Fixed an issue where we were not notifiying emby users. [Jamie.Rees] - -- Fixed the issue where the recent changes page was not showing the correct date. #1296. [Jamie.Rees] - -- Fixed #1252 (Show the correct user type on the management page for Plex Users) [Jamie.Rees] - -- Fixed the casting error #1292. [Jamie.Rees] - -- Fix test newletter not sending when empty. [Dhruv Bhavsar] - -- Quick fix for email false positive message. ISSUE: #1286. [Dhruv Bhavsar] - -- Fixes around the newsletter. We will now correctly show newly added shows and also newly added episodes. #1163. [tidusjar] - -- Fixed a sonarr deseralization error. [tidusjar] - -- Increased the delay for the Episode information api calls. #1163. [tidusjar] - -- Looks like we were overloading emby with out api calls. [tidusjar] - -- Fixed the root path escaping issue for Radarr too! [tidusjar] - -- Some small backend newsletter changes, we can now detect if there are any movies and/or tv shows, if there are none then we will no longer send out an empty newsletter. [Jamie.Rees] - -- Remoddeled the notificaiton settings to make it easier to add more. This is some techinical changes that no one except me will ever notice :( [Jamie.Rees] - -- Fixed #1234. [Jamie.Rees] - -- A fix to the about page and also started to rework the notification backend slightly to easily add more notifications. [Jamie.Rees] - -- Adding more logging into the Plex Cacher. [Jamie.Rees] - -- #1218 changed the text when we cannot display release notes for dev and EAP branches. [Jamie.Rees] - -- Fix for #1236. [SuperPotatoMen] - -- Tooltips. [Jamie.Rees] - -- #236. [Jamie.Rees] - -- #1102. [Jamie.Rees] - -- Done #1012. [Jamie.Rees] - -- Oops #1134. [Jamie.Rees] - -- Fixed #1121. [Jamie.Rees] - -- Fixed #1210. [Jamie.Rees] - -- Fixed typo #1134. [Jamie.Rees] - -- Fixed #1223. [Jamie.Rees] - -- Another newsletter fix attempt #1163 #1116. [tidusjar] - -- Fixup! Reset the branch on v2.1.0 tag to get to a shared state between dev and Master. [distaula] - -- Fixed a bug in the Plex Newsletter. [tidusjar] - -- Typo. [tidusjar] - -- Fixed around the newsletter and a small feature around the permissions/features (#1215) [Jamie] - -- Fixed #1189. [tidusjar] - -- Fixed #1195. [Jamie.Rees] - -- Fixed #1195. [Jamie.Rees] - -- Fixed #1192. [Jamie.Rees] - -- Fixed issue where we could get null rating keys on Plex. [tidusjar] - -- Needed to treat a 201 as success, too. + removed some commented out code. [Shaun McPeck] - -- Normalized spacing/tabs. [smcpeck] - -- Move local user login to be the first thing checked; renamed old Api variable to PlexApi now that Emby is in play. [smcpeck] - -- Remove all the polling/retry logic around HP requests. This was a problem do to not properly awaiting the initial AddArtist API call being sent to HP. Also fix SetAlbumStatus to use ReleaseId instead of MusicBrainsId (same fix previously applied to AddArtist). [smcpeck] - -- Restore checking of HTTP StatusCode on ApiRequests; remove checking of response.ErrorException. [smcpeck] - -- Reverted (for now) non-200 response handling; added some extra logging. [smcpeck] - -- Tweaked ApiRequest behavior on non-200 responses; think it was breaking login. :-" [smcpeck] - -- Only deserialize response payload in ApiRequest when StatusCode == 200. Will a default return value in other cases cause other issues? [smcpeck] - -- Headphones - added releaseID to generic RequestedModel and passing that through to HP request. Their API doesn't request via the MusicBrainzId. [smcpeck] - -- Fixed #1038. [tidusjar] - -- Fixed a slight issue where we could click the change folders button rather than the dropdown arrow #1189. [tidusjar] - -- Bunch of updater files. [tidusjar] - -- #1163 #117. [tidusjar] - -- Removed some unnecessary 'ConfigureAwait` uses. [smcpeck] - -- Remove meaningless html class from actor searching checkbox. [smcpeck] - -- Fixed an issue where we were not always showing movies from external programs. [tidusjar] - -- Remove extra delay when filtering out existing movies. [smcpeck] - -- Post merge build fixes. [smcpeck] - -- Fix. [tidusjar] - -- Fixed #1177. [tidusjar] - -- Fixed #1152. [tidusjar] - -- Fixed #1123. [tidusjar] - -- Fixed a bug when sending to radarr. [tidusjar] - -- Fixed #1133. [tidusjar] - -- Fixed issues img. [Jamie.Rees] - -- Stop Plex being enabled on the first time installing #1048. [Jamie.Rees] - -- The landing page now works for emby #435. [tidusjar] - -- Fixed #1104. [tidusjar] - -- Fixed #1090. [tidusjar] - -- Fixed #1103. [tidusjar] - -- Small changes. [tidusjar] - -- Break out Mass Email feature into its own tab, upgrade Font Awesome and clean up some comments. [dhruvb14] - -- Fix typo. [Travis Bybee] - -- Fixed #1066. [Jamie.Rees] - -- Fixed broken builds. [Jamie.Rees] - -- Fixed #1083. [Jamie.Rees] - -- #1049. [tidusjar] - -- Fixed #1071. [tidusjar] - -- Fixed #1048 #1081. [tidusjar] - -- #1074. [Jamie.Rees] - -- #1069. [Jamie.Rees] - -- Fix for #1068. [tidusjar] - -- Remove duplciate tv show status. [tidusjar] - -- Some request ui changes. [tidusjar] - -- Removed references to Plex. [tidusjar] - -- Removed plex from the scheduled jobs ui. [tidusjar] - -- First run of the newsletter set it to a test. [tidusjar] - -- Reworked the newsletter for Emby! Need to rework it for Plex and use the new way to do it. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed the mass email, it was only being set to users with the newsletter feature #358. [tidusjar] - -- Removed Plex Request from the notifications. [tidusjar] - -- Finish implementing mass email feature. [dhruvb14] - -- @tidusjar pointed out runtime error!! [dhruvb14] - -- Does not compile, need to get data from UI into nancy somehow and figure out why IMassEmail is not initializing. [dhruvb14] - -- Begin Implementing Mass Email Section. [dhruvb14] - -- Hide the auto update btn #236 Fixed where we were not populating the emby episodes #435. [tidusjar] - -- Fix Radarr labels. [d2dyno] - -- Fixed pace loader. [Jamie.Rees] - -- Check if Emby/Plex is enabled before starting the job. [Jamie.Rees] - -- Fixed #1036. [Jamie.Rees] - -- Fixed a typo and changed wording. [Torkil Liseth] - -- Fixed #1035. [Jamie.Rees] - -- Fix for #1026. [Jamie.Rees] - -- Fixed #1042. [tidusjar] - -- #435. [Jamie.Rees] - -- #435 Started the wizard. [Jamie.Rees] - -- Removed. [tidusjar] - -- Final Fixes. [dhruvb14] - -- Partial fix for broken HR tag's in Email... [dhruvb14] - -- DAMN! #435 that's a lot of code! [tidusjar] - -- Started adding Emby, Lots of backend work done. Need a few more services done and login and user management. #435. [tidusjar] - -- UI changes to add checkbox and support searching for only new matches via new API. [smcpeck] - -- REFACTOR: IAvailabilityChecker - changed arrays to IEnumerables. [smcpeck] - -- UI changes to consume actor searching API. [smcpeck] - -- API changes to allow for searching movies by actor. [smcpeck] - -- Enforcing async/await in synchronous methods that were marked async. [smcpeck] - - -## v2.1.0 (2017-01-31) - -### **New Features** - -- Update README.md. [Jamie] - -- Update .gitattributes. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added the new labels to the search. [tidusjar] - -- Added a switch to use the new search or not, just in case people do not like it. added a migration to turn on the new search. [Jamie.Rees] - -- Added a bunch of categories for tv search similar to what we have for movies. [Jamie.Rees] - -### **Fixes** - -- Fixed typos. [Haries Ramdhani] - -- Fix typo in readme. [tdorsey] - -- Fixed #985. [Jamie.Rees] - -- FIxed #978. [tidusjar] - -- Fixed the approval issue for #939. [tidusjar] - -- Some general improvements. [tidusjar] - -- Turned off migration for now. [tidusjar] - -- Fixed #998. [tidusjar] - -- Additional movie information. [Jamie.Rees] - -- Debug info around the notifications. [Jamie.Rees] - -- Small changes. [tidusjar] - -- Fixed #995. [tidusjar] - -- Fix for #978. [tidusjar] - -- Fixed #991. [tidusjar] - -- Fixed the login issue and pass Radarr the year #990. [Jamie.Rees] - -- More small tweaks around the username/alias. [Jamie.Rees] - -- Possible issue with the empty username. [Jamie.Rees] - -- Potential Fix for #985. [Jamie.Rees] - -- Small changed to the sidebar. [Jamie.Rees] - -- Small changes. [Jamie.Rees] - -- Done #627. [Jamie.Rees] - -- Finished #535 #445 #170. [tidusjar] - -- Fixed tests. [Jamie.Rees] - -- Started to add the specify Sonarr root folders. [Jamie.Rees] - -- Fixed #968. [Jamie.Rees] - -- Fixed #970. [Jamie.Rees] - -- Done #924. [Jamie.Rees] - -- Fixed. [Jamie.Rees] - -- Fixed #955. [Jamie.Rees] - -- #956. [Jamie.Rees] - -- Fixed #947. [Jamie.Rees] - -- #951. [tidusjar] - -- Finished #923 !!! [tidusjar] - -- More for #923. [Jamie.Rees] - -- Radarr integartion in progress #923. [tidusjar] - -- Fixed #940 don't show any shows without a tvdb id. [tidusjar] - -- Finished #739. [Jamie.Rees] - -- Initial impliementation of #739. [Jamie.Rees] - -- Improved the search UI and made it more consistant. Finished the Netflix API Part #884. [Jamie.Rees] - - -## v2.0.1 (2017-01-16) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added a netflix api. [Jamie.Rees] - -### **Fixes** - -- Fixed #934. [Jamie.Rees] - - -## v2.0 (2017-01-14) - -### **New Features** - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Added the settings for #925 but need to apply the settings to the UI. [Jamie.Rees] - -- Changed the settings name from Plex Requests to Ombi. [Jamie.Rees] - -- Added support for Managed Users #811. [Jamie.Rees] - -- Change solution name in travis. [mhann] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -### **Fixes** - -- Finished #925. [Jamie.Rees] - -- Some TODO's. [Jamie.Rees] - -- Fixed #915. [Jamie.Rees] - -- Fixed the issue where notifications are not being sent to users with Aliases #912. [Jamie.Rees] - -- Fixed #891. [Jamie.Rees] - -- Fix indentation issue. [Marcus Hann] - -- Implement simple button. [Marcus Hann] - -- Plex Username Case Sensitivity Fix. [thegame3202] - -- Fixed #882. [Jamie.Rees] - -- Api changed again, so more fixes for #878. [Jamie.Rees] - -- Possible fix for #893. [Jamie.Rees] - -- Fixed #898. [Jamie.Rees] - -- Fixed #878. [TidusJar] - -- * userManagementController.js: fixed #881. [TidusJar] - -- More work on watcher, should all be good now. #878. [Jamie.Rees] - -- Delete PlexRequests.sln.DotSettings. [Jamie] - -- Fixed #862. [Jamie.Rees] - -- #399 and #398 finished. [Jamie.Rees] - -- More work on #399. [Jamie.Rees] - -- Finished #884. [Jamie.Rees] - -- More for #844. [Jamie.Rees] - -- Another #844. [Jamie.Rees] - -- Fixed a dependancy issue with #844. [Jamie.Rees] - -- Finished the main part of #844 just need testing. [Jamie.Rees] - -- Fixed #832. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Fix tiny readme typo. [mhann] - -- Fixed #850 also started #844 (Wrote the API interaction) [Jamie.Rees] - -- #801 #292 done. [Jamie.Rees] - -- Should fix #841 #835 #810. [Jamie.Rees] - -- Fix small typo in ticket overview page. [mhann] - -- More work on the combined login. [Jamie.Rees] - -- Fixed db issue. [Jamie.Rees] - -- Name changes. [Jamie.Rees] - -- All Sln changes. [tidusjar] - -- Moved API Sln dir. [tidusjar] - -- Fixed build. [tidusjar] - -- Moved namespaces. [tidusjar] - -- Renamed zip. [tidusjar] - -- Product name change. [tidusjar] - - -## v1.10.1 (2016-12-17) - -### **Fixes** - -- #788 fixed! [tidusjar] - -- Fixed #788 and #791. [tidusjar] - -- #399 #398. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Small refactorings. [Jamie.Rees] - -- #782. [Jamie.Rees] - -- #785. [Jamie.Rees] - - -## v1.10.0 (2016-12-15) - -### **New Features** - -- Update README.md. [Jamie] - -- Added optional launch args for the auto updater. [Jamie.Rees] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update _Navbar.cshtml. [Jamie] - -- Update README.md. [Jamie] - -- Update _Navbar.cshtml. [Jamie] - -- Update README.md. [Jamie] - -- Added a new permission to bypass the request limit. [Jamie.Rees] - -- Update Version1100.cs. [SuperPotatoMen] - -- Added logging around the Newsletter #717. [Jamie.Rees] - -- Added missing migration. [tidusjar] - -- Added loading spinner. [Jamie.Rees] - -- Update UI.resx. [SuperPotatoMen] - -- Update Version1100.cs. [Jamie] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -### **Fixes** - -- Fixed an issue where the HTML in the newsletter was incorrect. [Jamie.Rees] - -- Fixed #201. [Jamie.Rees] - -- Fixed #720 and added better error handling around the migrations. [Jamie.Rees] - -- Fixed #769. [Jamie.Rees] - -- Write out the actual file version. [Jamie.Rees] - -- Error checking around GA. [TidusJar] - -- Fixed #761. [tidusjar] - -- Fixed #749 Fixed an issue where we were adding the read only permission when creating the admin. [tidusjar] - -- Fixed #757. [tidusjar] - -- Fixes around the server admin #754. [Jamie.Rees] - -- Removed the trace option from the UI, it is only accessible when appending the url with "?developer" #753. [Jamie.Rees] - -- Fixed an issue where the admin could not be updated. [Jamie.Rees] - -- Fixed #745. [Jamie.Rees] - -- Fixed #748. [Jamie.Rees] - -- Workaround for #748. [SuperPotatoMen] - -- Another attempt to fix #717. [tidusjar] - -- Fixed #744. [Jamie.Rees] - -- Tidied up the warnings. [Jamie.Rees] - -- Small bit of analytics. [Jamie.Rees] - -- Removed the whitelist. [Jamie.Rees] - -- Should fix #696 Fixed an issue with the scheduled jobs where it could use a different trigger if the order between the schedules and the triggers were in different positions in the array... Stupid me, ordering both arrays by the name now. [tidusjar] - -- Some better null object handling #731. [Jamie.Rees] - -- Small tweaks. [Jamie.Rees] - -- Fixed #728. [Jamie.Rees] - -- Fixed #727. [Jamie.Rees] - -- Fixed admin redirect issue. [tidusjar] - -- Fixed #718. [tidusjar] - -- Lots of small fixes and tweaks. [Jamie.Rees] - -- Tidied up some of the angular code, split the UI into it's own directives for easier maintainability. [Jamie.Rees] - -- Small tweaks to the Request Page. [Jamie.Rees] - -- Reverted the PR that may have caused #619. [Jamie.Rees] - -- Some small tweaks around #218 Just added the link to the settings and some angular improvements. [Jamie.Rees] - -- Test. [Jamie.Rees] - -- Attempt at fixing #686. [Jamie.Rees] - -- Finished #707. [Jamie.Rees] - -- Fixed #704. [Jamie.Rees] - -- Fixed #705. [Jamie.Rees] - -- Fixed #706. [Jamie.Rees] - -- #547. [Jamie.Rees] - -- Default tabs #304. [Jamie.Rees] - -- Fixed #703. [Jamie.Rees] - -- #233. [Jamie.Rees] - -- Done #678. [Jamie.Rees] - -- Fixed #670. [Jamie.Rees] - -- #456 Update all the requests when we identify that the username changes. [Jamie.Rees] - -- More user management. [Jamie.Rees] - -- #218. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Implimented the features #218. [tidusjar] - -- Use the user alias everywhere if it is set #218. [tidusjar] - -- Implimented auto approve permissions #218. [tidusjar] - -- A. [tidusjar] - -- Fixed an IOC issue. [tidusjar] - -- Fixed the issue with user management, needed to implement our own authentication provider. [Jamie.Rees] - -- Small changes including #666. [Jamie.Rees] - -- Done #679. [tidusjar] - -- Reduce the retry time. [Jamie.Rees] - -- Remove all references to the claims. [Jamie.Rees] - -- Lots of fixed and stuff. [Jamie.Rees] - -- Fixed potential crash #683. [Jamie.Rees] - -- Fixed #681. [Jamie.Rees] - -- More user management. [Jamie.Rees] - -- Finishing off the user management page #218 #359 #195. [Jamie.Rees] - -- Finished #646 and fixed #664. [Jamie.Rees] - -- Started on #646. Fixed #657. [Jamie.Rees] - -- Fixed #665. [Jamie.Rees] - -- Migrate users. [TidusJar] - -- Fixed build. [Jamie.Rees] - -- Convert the for to foreach for better readability. Still need to rework this area. [Jamie.Rees] - -- Final Tweaks #483. [Jamie.Rees] - -- Finished #483. [Jamie.Rees] - -- Finished the queue #483. [Jamie.Rees] - -- Started on the queue for requests #483 TV Requests with missing information has been completed. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Finished the notification for the fault queue. [Jamie.Rees] - -- Finished #556. [Jamie.Rees] - -- Finished #633 (First part of the queuing) [Jamie.Rees] - -- Finished #659 #236 has been modified slightly. Needs testing on Different systems. [Jamie.Rees] - -- Almost finished #659. [Jamie.Rees] - -- Started on #483. [Jamie.Rees] - -- #544. [Jamie.Rees] - -- Fixed #656 and more work on #218. [Jamie.Rees] - -- Fixed some issues with the user management work. [TidusJar] - -- Fixed build issue. [TidusJar] - -- User perms. [Jamie.Rees] - - -## v1.9.7 (2016-11-02) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Potential fix for #629. [TidusJar] - -- Fixed an issue to stop blatting over the base url. [tidusjar] - -- Fixed #643. [TidusJar] - -- Fixed #622. [TidusJar] - - -## v1.9.6 (2016-10-28) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fixed #586. [Jamie.Rees] - -- Fixed #622. [Jamie.Rees] - -- Fixed #621. [Jamie.Rees] - - -## v1.9.5 (2016-10-27) - -### **New Features** - -- Added our own custom migrations, a lot easier to migrate DB versions now. [tidusjar] - -### **Fixes** - -- Bump version. [Jamie.Rees] - -- Small bit of work on the user claims. [Jamie.Rees] - -- Fix #612 again. [Jamie.Rees] - -- User management styling. [Jamie.Rees] - -- Fixed #608 and some other small stuff. [tidusjar] - -- More user mapping. [tidusjar] - -- Fixed #615. [tidusjar] - -- Fixed #610. [tidusjar] - -- User management stuff. [Jamie.Rees] - -- User management work. [Jamie.Rees] - -- Revert the TVSender to use the old code. [Jamie.Rees] - -- Fixed the view issue. [tidusjar] - -- S582: admin improvements part 2. [Jim MacKenzie] - -- Fix #612. [Jamie.Rees] - -- User management, migration and newsletter. [Jamie.Rees] - -- #602 recently added improvements. [tidusjar] - -- Revert "Sorting out the current state of migrations" [Jamie.Rees] - -- Sorting out the current state of migrations. [Jamie.Rees] - -- Marked as obsolete. [Jim MacKenzie] - -- Migration setup. [Jim MacKenzie] - -- Removed extra line breaks. [Jim MacKenzie] - -- Moved Newsletter Settings to its own page. [Jim MacKenzie] - -- Reverted TMDB package. [Jamie.Rees] - -- Remove DB Option. [Jamie.Rees] - -- Upgrade the movie DB package and fixed #370 To fix this I had to make another API call... It slows down the search... [tidusjar] - -- Lots of small fixes including #475. [tidusjar] - -- A better fix for #587. [tidusjar] - -- Fixed #553. [tidusjar] - -- #601. [Jamie.Rees] - -- More rework to use the Plex DB. [Jamie.Rees] - -- More work around using the PlexDatabase. [Jamie.Rees] - -- Plex DB. [Jamie.Rees] - -- Allow to process even know we had an error #578. [Jamie.Rees] - -- Fix boostrapper-datetimepicker imports (#586) [David Torosyan] - -- Potential work around for #587. [tidusjar] - -- Fixed #589. [tidusjar] - - -## v1.9.4 (2016-10-10) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added Paypalme options, no UI yet (#568) [Jim MacKenize] - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -### **Fixes** - -- Reverted. [tidusjar] - -- Make sure it's enabled before sending the recently added. [tidusjar] - -- Moved the HR inside the table for TV Shows. [Jamie.Rees] - -- FIXED!!!!! YES BITCH! #550. [tidusjar] - -- Moved the horizontal rules inside the table row. [tidusjar] - - -## v1.9.3 (2016-10-09) - -### **New Features** - -- Added properties to disable tv requests for specific episodes or seasons and wired up to admin settings. [Matt McHughes] - -- Added different sonarr search commands. [tidusjar] - -### **Fixes** - -- Fixed #515. [tidusjar] - -- Fixed #561 and a small bit of work on #569. [tidusjar] - -- #569. [tidusjar] - -- Fixed case typo. [Matt McHughes] - -- Finished wiring tv request settings to tv search. [Matt McHughes] - -- WIP hide tv request options based on admin settings. [Matt McHughes] - -- Set meta charset to be utf-8. [Madeleine Schnemann] - -- F#552: updated labels text. [Jim MacKenize] - -- F#552: Re-design lables. [Jim MacKenzie] - -- Last correction.. Now the translation is ready to be used. [Michael Reber] - -- Forgot to correct two incorrect translations. [Michael Reber] - -- Correction of the German translation. [Michael Reber] - -- Notification improvements. [tidusjar] - -- #515. [tidusjar] - - -## v1.9.2 (2016-09-18) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update CouchPotatoCacher.cs. [Jamie] - -- Added some error handing around the GetMovie area #517. [tidusjar] - -- Added a version endpoint in "/api/version" #529. [tidusjar] - -### **Fixes** - -- Trying to fix the auto CP. [tidusjar] - -- Increase the notice message text box #527. [tidusjar] - -- #536 this should fix notification settings when it is being unsubscribed when testing. [tidusjar] - -- Improved how the TV search looks and feels. [tidusjar] - -- Fix for reverse proxy when using the wizard. [Devin Buhl] - -- Fixed #532. [tidusjar] - -- This should fix some issues with the episode requests #514. [tidusjar] - -- Small changes around existing series. [tidusjar] - -- Fixed #514 and the unit tests. [tidusjar] - -- If there is a bad password when changing it, we now inform the user. [tidusjar] - -- When logging out as admin remove the username from the session. [tidusjar] - -- Sorted out some of the UI for #18. [tidusjar] - -- Finished #18. [tidusjar] - - -## v1.9.1 (2016-08-30) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added french to the navbar. [tidusjar] - -- Changed the way we use the setTimeout function. Should fix #403 #491 #492. [tidusjar] - -- Change the redirection to use a relative uri redirect #473. [tidusjar] - -### **Fixes** - -- Fixed tests. [tidusjar] - -- Fixed #491 and added more logging around the email messages under the Info level. [tidusjar] - -- Finished #415. [tidusjar] - -- Fixed an issue where there were some JS errors on the landing page settings and stopped us being redirected to the login sometimes as an admin. [tidusjar] - -- Fixed #480. [tidusjar] - -- User management. [tidusjar] - -- Fixed #505. [tidusjar] - -- Append the application version to the end of our JS/CSS files. [tidusjar] - -- Fixed issue #487. [tidusjar] - -- Remove the datetime picker css from the main assets block and only load it on the pages it needs. #493. [tidusjar] - -- Redirect to search if we are already logged in #488. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed an issue where you could set the base url as requests #479. [tidusjar] - -- Working on the beta releases page and also the user management. [tidusjar] - -- User work. [tidusjar] - - -## v1.9.0 (2016-08-18) - -### **New Features** - -- Update the availability checker to search for TV Episodes. [tidusjar] - -- Changed the no TVMazeid message. [tidusjar] - -- Added an option to disable/enable the Plex episode cacher. [tidusjar] - -- Updated the episode cacher to have a minimum of 11 hours before it runs again. [tidusjar] - -- Added some useful analytical infomation around the wizard. [tidusjar] - -- Updated the German translations #402. [tidusjar] - -- Added some code to shrink the DB. reworked the search to speed it up. [tidusjar] - -- Change to use the GrandparentTitle rather than the thumbnail.... facepalm. [tidusjar] - -- Change the interval to hours! [tidusjar] - -- Added the transaction back into the DB. Do not run the episode cacher if it's been run in the last hour. [tidusjar] - -- Added logging. [tidusjar] - -- Changed the query slightly. [tidusjar] - -- Updated Newtonsoft.Json, Autofixture, Nlog and Dapper packages. [tidusjar] - -- Added the Sonarr check for episodes #254. [tidusjar] - -- Added unit tests. [tidusjar] - -- Added #436. [tidusjar] - -- Update build no. [tidusjar] - -- Updated translations for #402. [tidusjar] - -- Added a beta module. [tidusjar] - -- Added a custom debug root path provider, this means we do not have to recompile the views every time we make a view change. [tidusjar] - -- Update .gitignore. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added tests for the string hash. [tidusjar] - -- Added code to request the api key for CouchPotato. [tidusjar] - -- Added the file version to the layout. [tidusjar] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added automation tests. [tidusjar] - -- Update appveyor.yml. [Jamie] - -- Updated packages. [tidusjar] - -- Updated Polly. [tidusjar] - -- Updated Fr, IT and NL translations #402. [tidusjar] - -- Changed the way the donate button works for #414. [tidusjar] - -- Added MediatR. [tidusjar] - -### **Fixes** - -- User management stuff. [tidusjar] - -- Fixed! [tidusjar] - -- Small amount of work on the user management. [tidusjar] - -- Fixed #466. [tidusjar] - -- Fixes. [tidusjar] - -- Made the episode request better. [tidusjar] - -- Removed commented out tests. [tidusjar] - -- Fixed the bad test after the merge. [tidusjar] - -- Reworked #466. [tidusjar] - -- More unit tests around the login and also the core Plex Checker. [tidusjar] - -- Potentially fixed the issue where we were requesting everything that was also available now. [tidusjar] - -- This should fix #466. [tidusjar] - -- Attempt at fixing a potential bug found from #466. [tidusjar] - -- #464 fixed. [tidusjar] - -- Fixed the build. [tidusjar] - -- Small improvements to the wizard. [tidusjar] - -- Always set the wizard to be true when editing the Plex Requests settings (Since the flag is not in the UI, a bool defaults to false). [tidusjar] - -- Tiny bit of more info. [tidusjar] - -- Finished #459. [tidusjar] - -- #459 is almost done. [tidusjar] - -- Modified the episode modal so that we are now resetting the button after a request. [tidusjar] - -- Commented out the transaction for now to debug it. [tidusjar] - -- Since we are multithreading, we should use a threadsafe type to store the episodes to prevent any threading or race conditions. [tidusjar] - -- Wrapped the bulk insert inside a transaction. [tidusjar] - -- Made the episode check parallel. [tidusjar] - -- Log out the GUID causing the issue. [tidusjar] - -- Fixed another test. [tidusjar] - -- Fixed tests. [tidusjar] - -- Got mostly everything working for #254 Ready for testing. [tidusjar] - -- Fixed issue with saving to db. [tidusjar] - -- Need to work out why the cacher is not working and where the datatype mismatch is. [tidusjar] - -- Don't delete first. [tidusjar] - -- Fix the log path issue #451. [tidusjar] - -- Dump an item. [tidusjar] - -- Small change with the return value in the batch insert. [tidusjar] - -- #254 Removed the cache, we are now storing the plex information into the database. [tidusjar] - -- Small change in the episode saver. [tidusjar] - -- Some small tweaks to improve the memory alloc. [tidusjar] - -- Short circuit when Plex hasn't been setup. Added Miniprofiler. [tidusjar] - -- Consolidate newtonsoft.json packages. [tidusjar] - -- Some performance improvements around the new TV stuff. [tidusjar] - -- Reworked the cacher, fixed the memory leak. No more logging within tight loops. [tidusjar] - -- Another null check. [tidusjar] - -- Some more changes. [tidusjar] - -- Some error handling. [tidusjar] - -- Check if the sonarr ep is monitored. [tidusjar] - -- Some logging. [tidusjar] - -- Small changes, we will actually see the episode cacher on the scheduled jobs page now. [tidusjar] - -- Work on the UI to show what episodes have been requested #254. [tidusjar] - -- Small fix. [tidusjar] - -- Fix the api change in #450. [tidusjar] - -- #254. [tidusjar] - -- Workaround for #440. [tidusjar] - -- Async async async improvements. [tidusjar] - -- Finished #266 Added a new cacher job to cache all episodes in Plex. [tidusjar] - -- Fixed #442. [tidusjar] - -- #254. [tidusjar] - -- Work around the sonarr bug #254. [tidusjar] - -- #254 having an issue with Sonarr. [tidusjar] - -- Small bit of work on #266. [tidusjar] - -- #254. [tidusjar] - -- Precheck and disable the episode boxes if we already have requested it. TODO check sonarr to see if it's already there. #254. [tidusjar] - -- Fixed broken build. [tidusjar] - -- More work for #254. [tidusjar] - -- More work on #254. [tidusjar] - -- Fixed the bug in #438 and added unit tests to make so we dont break it in the future. [tidusjar] - -- Some reason we had dupe translations. [tidusjar] - -- Rename SubDir to Base Url. [tidusjar] - -- Fix the exception in #440. [tidusjar] - -- Reworking the login page for #426. [tidusjar] - -- Fixed #438. [tidusjar] - -- Finished the auth stuff. [tidusjar] - -- Finished up the SMTP side of #429. [tidusjar] - -- #428 Added a message when the we cannot get a TVMaze ID. [tidusjar] - -- #254 MOSTLY DONE! At last, this took a while. [tidusjar] - -- Removed the other rootpath provider. [TidusJar] - -- Removed the other rootpath provider. [TidusJar] - -- Removed the other rootpath provider. [TidusJar] - -- Should fix #429. [TidusJar] - -- Done #135 We are including the application version number in the directory. [tidusjar] - -- #387 trim the spaces from the api key. Tidied up the setting models a bit. [tidusjar] - -- Wrapped the repo to catch Sqlite corrupt messages. [tidusjar] - -- Frontend and tv episodes api work for #254. [tidusjar] - -- #424. [tidusjar] - -- #359. [tidusjar] - -- Moved the plex auth token to the plex settings where it should belong. [tidusjar] - -- Small changes around the user management. [tidusjar] - -- Missed brace. [tidusjar] - -- Removed. [tidusjar] - -- Fixed issues from the merge. [tidusjar] - -- Stupid &$(* merge. [tidusjar] - -- Angular. [tidusjar] - -- Reworked the custom notifications... again. Need to figure out how to find the view to the model. [tidusjar] - -- Fixed #417. [tidusjar] - -- Removed NinjectConventions, we hadn't started to use it anyway. [tidusjar] - -- Fixed the way we will be using custom messages. [tidusjar] - -- Test checkin. [tidusjar] - -- Better handling for #388. [tidusjar] - -- Fixed #412. [tidusjar] - -- Fixed #413. [tidusjar] - -- Fixed #409. [tidusjar] - -- Trycatch around the availbility checker. [tidusjar] - -- WIP on notification resolver. [tidusjar] - -- Tidy. [tidusjar] - -- Plugged in MediatR. [tidusjar] - -- Moved over to using Ninject. [tidusjar] - - -## v1.8.4 (2016-06-30) - -### **Fixes** - -- Fixed the bug where we were auto approving everything. Added French language into the navigation bar. [tidusjar] - - -## v1.8.3 (2016-06-29) - -### **New Features** - -- Update README.md. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added some of the backend bits for #182. [tidusjar] - -- Updates for #243. [tidusjar] - -- Added Dutch language #243. [tidusjar] - -- Added languages #243. [tidusjar] - -- Added logging #350. [tidusjar] - -### **Fixes** - -- Small changes. [tidusjar] - -- Allow html in the notice message. [tidusjar] - -- Some more unit tests around the NotificationMessageResolver. [tidusjar] - -- Fixed a timing bug found the in build. Note, when working with time differences use TotalDays. [tidusjar] - -- More translations on the search page (Mainly the notification messages) #243. [tidusjar] - -- Fixed some warnings. [tidusjar] - -- CodeCleanup. [tidusjar] - -- Fixed a bit of a stupid bug in the resetter and added unit tests around it to make sure this never happens again. [tidusjar] - -- Fixed an issue where we didn't provide the correct response when clearing the logs. [tidusjar] - -- Made it so users that are in the whitelist do not have a request limit. [tidusjar] - -- Made it so the request limit doesn't apply to admin users. [tidusjar] - -- Fixed where a user could see the delete button on the issues page. [tidusjar] - -- Fixed some small issues and improved the navbar. [tidusjar] - -- Translated the Requested page #243. [tidusjar] - -- Finished #337. [tidusjar] - -- Some analytics. [tidusjar] - -- More translations for #243 and welcome text for #293. [tidusjar] - -- Small bit of work for #359. [tidusjar] - -- Finished #6. [tidusjar] - -- Analytics and fixes. [tidusjar] - -- Translated the search page #243. [tidusjar] - -- Implemented the different languages and added the ability to change cultures. #243. [tidusjar] - -- Started #243. [tidusjar] - -- Fixed #364. [tidusjar] - -- Some more useful analytical information. [tidusjar] - -- Generic try catch to fix #350. [tidusjar] - -- Slight changes, moved the donate button. [tidusjar] - -- Potential fix for #350. [tidusjar] - -- Better way of obtaining clean enum string. [Drewster727] - -- Fixed #362. [tidusjar] - - -## v1.8.2 (2016-06-22) - -### **New Features** - -- Update readme. [tidusjar] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fixed a circular reference issue. [tidusjar] - -- Small changes around how we work with custom events in the analytics. [tidusjar] - -- Fixed #353 #354 #355. [tidusjar] - -- Null provider check for movies. [Drewster727] - -- Show request type in notifications #346 and fix an issue from previous commit for #345. [Drewster727] - -- Add an option to stop sending notifications for requests that don't require approval #345. [Drewster727] - - -## v1.8.1 (2016-06-21) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fix obj ref error when scheduler runs (ProviderId is null?) [Drewster727] - -- Fix logic for obtaining a sonarr quality profile #340. [Drewster727] - - -## v1.8.0 (2016-06-21) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the new advanced search into the search page too. [tidusjar] - -- Change the way we configure the IoC container in the bootstrapper, we are registering all the concrete instances on application start rather than on each web request. This should increase the performance per HTTP request. [tidusjar] - -- Updated nlog and fixed #295. [tidusjar] - -### **Fixes** - -- Workaround for #334. [Drewster727] - -- Create .gitattributes. [Jamie] - -- Fixes to the issues. [tidusjar] - -- Set the defaults for the landing page. [tidusjar] - -- Revert branch to 664dae2. [tidusjar] - -- Some unit tests for the issues. [tidusjar] - -- Tidied up the bootstrapper. [tidusjar] - -- Fix up landing page UI. [Drewster727] - -- Fixed CSS issue with the top arrow in the Plex theme. [tidusjar] - -- Small changes. [tidusjar] - -- Done #318. [tidusjar] - -- Fixed tests. [tidusjar] - -- #298 added some tests for the landing page. [tidusjar] - -- We are now only keeping the latest 1000 log records in the database. Delete everything else. [tidusjar] - -- Some analytic stuff. [tidusjar] - -- Capture the TVDBID when requesting. [tidusjar] - -- Attempting to improve #219. [tidusjar] - -- Just some more async changes. [tidusjar] - -- Small changes. [tidusjar] - -- More work on #298. Everything wired up. [tidusjar] - -- Fixed the issue on the landing page #298. [tidusjar] - -- #298 moved the content to the left a bit. [tidusjar] - -- Styling for #298 done, just need to wire up the model and do the actual status check. [tidusjar] - -- Bumped up the version number. [tidusjar] - -- Removed some DumpJson() from the trace logs. [tidusjar] - -- Small ui fix (100% width user/password fields to improve mobile experience) [Drewster727] - -- Landing page stuff #298. [tidusjar] - -- Datepicker UI fixes + small landing page UI fix. [Drewster727] - -- Removed a change that shoudn't have been commited. [tidusjar] - -- Fixed tests. [tidusjar] - -- More work for #298. [tidusjar] - -- #273 added for only available content on the search. [tidusjar] - -- Fixed #303 Looks like there was some incorrect business logic. [tidusjar] - -- Most of #273 done. [tidusjar] - -- Settings done for #298. [tidusjar] - -- Started #298. [tidusjar] - -- A crap tonne of work on #273. [tidusjar] - -- More work on #273. [tidusjar] - -- Reduced kept logs for 2 days. [tidusjar] - -- Fixed #300. [tidusjar] - -- #273. [tidusjar] - -- Fixed a bug with some users with the CP profiles. [tidusjar] - -- #273. [tidusjar] - -- Done the same for TV. [tidusjar] - -- Fixes #296. [tidusjar] - -- More for #273. [tidusjar] - -- Small changes. [tidusjar] - -- Revert "Small changes" [tidusjar] - -- Small changes. [tidusjar] - -- Finished #221 and added more async #278. [tidusjar] - -- Spelling mistake in the html! this fixes #264. [tidusjar] - -- More work on #273. [tidusjar] - -- Fixed #210. [tidusjar] - -- Started #273. [tidusjar] - - -## v1.7.5 (2016-05-29) - -### **New Features** - -- Update preview. [Jamie] - -- Updated dapper.contrib. Looks like there was a bug in the async methods. [tidusjar] - -- Updater wouldn't work when running a reverse proxy #236. [tidusjar] - -### **Fixes** - -- Bump build ver. [tidusjar] - -- Use HTTPS for the poster images, so there aren't any mixed content warnings when serving the application via an HTTPS reverse proxy. [Sean Callinan] - -- Removed static declarations. [tidusjar] - -- Fixed styling on modal. [tidusjar] - -- Made the search page all async goodness #278. [tidusjar] - -- Made the request module async #278. [tidusjar] - -- Started some dynamic scrolling. [tidusjar] - -- Stop dumping out the settings to the log. [tidusjar] - -- Made more async goodness. [tidusjar] - -- Made some of the searching async #278. [tidusjar] - -- Fixed #277. [tidusjar] - -- Reworked some tests. [tidusjar] - -- #26q make the auth users list taller. [Drewster727] - -- Fix 404 error. [Drewster727] - -- #262 make the auth users list taller. [Drewster727] - -- #221 delete requests per category. [Drewster727] - -- #256 #237 UI Improvements and consolidation. [Drewster727] - -- Fixed a bug in the user notification where if an admin wants to be notified they wouldn't be. [tidusjar] - -- Set the admin to have all claims. [tidusjar] - -- Fix null exception possibility in cp/sickrage cacher classes. [Drewster727] - -- Fixed #244. [tidusjar] - -- Fixed #240. [tidusjar] - -- Fixed #270. [tidusjar] - -- Fixed an issue where if you have only 1 plex friend it would not show in the list. [tidusjar] - - -## v1.7.4 (2016-05-25) - -### **New Features** - -- Update README.md. [Jamie] - -### **Fixes** - -- Fixed #252. [tidusjar] - -- Fixed #428. [tidusjar] - -- Version bump. [tidusjar] - -- Fixed tests. [tidusjar] - -- Fully fixed #239. [tidusjar] - -- We wan't updating the DB schema. [tidusjar] - - -## v1.7.3 (2016-05-25) - -### **Fixes** - -- Fixed the release build issue where we could not access the settings #239. [tidusjar] - - -## v1.7.2 (2016-05-25) - -### **Fixes** - -- Fixed a small bug where an exception would get thrown. [tidusjar] - -- Build version bump. [tidusjar] - -- Cleanup. [tidusjar] - -- Typo. [tidusjar] - -- Fixed #241. [tidusjar] - -- Fixed #239. [tidusjar] - -- Fixed #238. [tidusjar] - -- Small UI tweaks/improvements. [Drewster727] - - -## v1.7.1 (2016-05-24) - -### **New Features** - -- Update version. [tidusjar] - -### **Fixes** - -- Fixed an issue with the auth page when running with a reverse proxy. [tidusjar] - - -## v1.7 (2016-05-24) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the ability to get the apikey from the api if you provide a correct username and password. Added more unit tests Added the ability to change a users password using the api refactored the Usermapper and made it unit testsable. [tidusjar] - -- Update. [tidusjar] - -- Added in an audit table. Since we are now allowing multiple users to change and modify things we need to audit this. [TidusJar] - -- Added the updater to the soloution and did a bit of starting code. [TidusJar] - -- Updated the claims so we can support more users. Added a user management section (not yet complete) Added the api to the solution and a api key in the settings (currently only gets the requests). [TidusJar] - -- Updated packages. [TidusJar] - -- Added a retry handler into the solution. We can now retry failed api requests. [TidusJar] - -- Update README.md. [Jamie] - -- Added Released propety to RequestViewModel. Added Released filter to the Requests page. [Chris Lees] - -- Added #27 to albums. [tidusjar] - -- Added the actual notification part of #27. [tidusjar] - -- Added the missing baseurl bit on the login page for #72. [tidusjar] - -- Added the 'enable user notifications' to the email settings view and model. [tidusjar] - -- Update README.md. [Jamie] - -### **Fixes** - -- Remove pointless test, change the default theme and fix a small bug. [tidusjar] - -- Fixed api. [tidusjar] - -- Finished #26. [tidusjar] - -- Plex theme. [tidusjar] - -- Implimented a theme changer, waiting for the Plex theme. [tidusjar] - -- Finished #222 #205. [tidusjar] - -- Started working on #26. [tidusjar] - -- Undid some small changes that was checked in by accident. [tidusjar] - -- #164 has been resolved. [tidusjar] - -- Resolved #224 , Removed the 'SSL' option from the email notification settings. We will now use the correct secure socket options (SSL/TLS) for your email host. [tidusjar] - -- Small changes. [tidusjar] - -- #27 fully finished. [tidusjar] - -- Fixed #215. [tidusjar] - -- Using Mailkit to fix #204. [tidusjar] - -- Color. [tidusjar] - -- Fully finished #27 just need to test it! [tidusjar] - -- Fixed test. [tidusjar] - -- Styling for #27. [tidusjar] - -- I think the auto updater is finished! #29. [tidusjar] - -- I think we have finished the main bulk of the auto updater #29. [tidusjar] - -- #222 #205 more ! Started getting the settings out. [tidusjar] - -- Removed the service locator from the base classes and added in some Api tests added all the tests back in! [tidusjar] - -- More work on the api and documentation #222 #205. [tidusjar] - -- Started documenting the API we now have swagger under ~/apidocs #222 #205. [tidusjar] - -- Api work for #205 Refactored how we check if the user has a valid api key Added POST request, PUT and DELTE. [tidusjar] - -- First pass of the updater working. #29. [tidusjar] - -- Removed SIGHUP from the termination list #220. [tidusjar] - -- Fixed. [tidusjar] - -- Missing. [tidusjar] - -- Missed out a file. [TidusJar] - -- And some more... [TidusJar] - -- Missed some files. [TidusJar] - -- A bit more work on switching to using user claims so we can support multiple users. [TidusJar] - -- Made the store backup clean up some of the older backups (> 7 days). [TidusJar] - -- More work on the user management. [TidusJar] - -- - Notifications will no longer be send to the admins if they request something. - Looks like we missed out adding the notifications to Music requests, so I added that in. [TidusJar] - -- - Improved the RetryHandler. - Made the tester buttons on the settings pages a bit more robust and added an indication when it's testing (spinner) [TidusJar] - -- Packages. [TidusJar] - -- Nm, [TidusJar] - -- Downgraded packages. [TidusJar] - -- Better handling for #202. [TidusJar] - -- Finished #208 and #202. [TidusJar] - -- This should help #202. [TidusJar] - -- Resolved #209. [TidusJar] - -- Finished #209. [TidusJar] - -- Slight adjustments to #189. [tidusjar] - -- - Added a visual indication on the UI to tell the admin there is a update available. - We are now also recording the last scheduled run in the database. [tidusjar] - -- Did the login bit on #185. [tidusjar] - -- Finished #186. [tidusjar] - -- Fixed #185. [tidusjar] - -- Fixed issue in #27 with albums. [tidusjar] - -- #27 added TV Search to the notification. [tidusjar] - -- Fixed bug. [tidusjar] - -- More work on #27 Added a new notify button to the search UI (Needs styling). Also fixed a bug where if the user could only see their own requests, if they search for something that has been requested, it will show as requested. [tidusjar] - -- Improved the startup of the application. We now properaly parse any args passed into the console. [tidusjar] - -- Additional cacher error handling + don't bother checking the requests when we don't get data back from plex. [Drewster727] - -- Remove old migration code and added new migration code. [tidusjar] - -- Stop the Cachers from bombing out when the response from the 3rd party api returns an exception or invalid response. #171. [tidusjar] - -- Increase the scheduler cache timeframe to avoid losing cache when the remote api endpoints go offline (due to a reboot or some other reason) -- if they're online, the cache will get refreshed every 10 minutes like normal. [Drewster727] - -- Fix the cacher by adding locking + extra logging in the plex checker + use a const key for scheduler caching time. [Drewster727] - -- Small changes. [tidusjar] - -- Switched out the schedulers, this seems to be a better implimentation to the previous and is easier to add new "jobs" in. [tidusjar] - -- Fixed #168. [tidusjar] - -- Fixed #162. [tidusjar] - -- Fix saving the log level. [Drewster727] - -- Set the max json length (fixes large json response errors) [Drewster727] - - -## v1.6.1 (2016-04-16) - -### **New Features** - -- Update README.md. [Jamie] - -- Added a url base. [tidusjar] - -- Change default logging. [tidusjar] - -- Added logging around SickRage. [tidusjar] - -### **Fixes** - -- Bump up the version number ready for the release. [tidusjar] - -- BaseUrl is finally finished! #72. [tidusjar] - -- #72 Login page done. [tidusjar] - -- More changes for the urlbase #72. [tidusjar] - -- Done the auth, cp, logs and sidebar for #72. [tidusjar] - -- Add an extra check when determining if a tv show is already available (also check if it starts with the show name returned from the tv db) [Drewster727] - -- Cache plex library data regardless of whether we have requests in the database or not. [Drewster727] - -- By default don't use a url base. [tidusjar] - -- Return empty array when obtaining queued IDs in sickrage cacher. [Drewster727] - -- Fixed a small bug in the SR cacher. [tidusjar] - -- Fixed when we do not have a base. [tidusjar] - -- More changes for #72. [tidusjar] - -- Fixed exception and all areas will now use the base url #72. [tidusjar] - -- Removed the test code from #72. [tidusjar] - -- Commented out the unit tests as they need to be reworked now. [tidusjar] - -- Finally fixed #72. [tidusjar] - -- Remove test code from plex api GetLibrary method. [Drewster727] - -- Finished up the caching TODO's. [tidusjar] - -- Kick off the schedulers once the web app has started (fixes api errors on start) [Drewster727] - -- Converted the UI back down to .NET 4.5.2. [tidusjar] - -- Fixed #154. [tidusjar] - -- Revert everything (except PlexRequests.UI) back to .NET 4.5.2 -- fixes incompatibilities with the latest version of mono (4.2.3.4) -- fixes notifications not working #152 #147 #141. [Drewster727] - -- #150 start caching plex media as well. refactored the availability checker. NEEDS TESTING. also, we need to make the Requests hit the plex api directly rather than hitting the cache as it does now. [Drewster727] - -- #150 split out the cache subscriptions to make sure they subscribe properly. [Drewster727] - -- #150 sonarr/sickrage cache checking. sickrage has a couple small items left. [Drewster727] - -- Fixed args. [tidusjar] - -- Fixed. [tidusjar] - -- Made the base better. [tidusjar] - -- Remove couchpotato api test code. [Drewster727] - -- Start the initial couchpotato cache call on a separate thread to keep the startup process quick. [Drewster727] - -- Add csproj with file changes from previous commit. [Drewster727] - -- Cache the couchpotato wanted list, update it on an interval, and use it to determine if a movie has been queued already. [Drewster727] - -- I think i've fixed an issue where SickRage reports Show not found. [tidusjar] - -- Set the default log level to info. #141. [tidusjar] - -- #125 refactor async task logic to work with mono. [Drewster727] - -- Fix search spinner sticking around after clearing search text + make the "Requested" and "Available" indicators in the search page different colors. [Drewster727] - -- #125 start indicating in the results if an item is already requested or available. [Drewster727] - -- #145 firefox css dsplay issue. [Drewster727] - -- Fixes for sonarr, we now display the error messages back to the user. [tidusjar] - -- Fixed #144. [tidusjar] - - -## v1.6.0 (2016-04-06) - -### **New Features** - -- Changed the build number. [tidusjar] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Changed the title to a contains but the artist still must match, [tidusjar] - -- Added unit tests to cover the new changes to the availability checker. [tidusjar] - -- Added the music check in the Plex Checker. [tidusjar] - -- Changed around the startup so we cache the profiles after the DB has been created. [tidusjar] - -- Updated where we update the request blobs schema change. [tidusjar] - -- Update SearchModule.cs. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Change the new columns type. [tidusjar] - -- Added a DBSchema so we have an easier way to update the DB. [tidusjar] - -- Added an issue template. [tidusjar] - -- Update README.md. [Jamie] - -- Added back the username into the Session when the admin logs in. This means they do not have to log in twice. [tidusjar] - -- Added happy path tests for the Checker. [tidusjar] - -- Added music to the search and requests page. [tidusjar] - -- Added a scroll to the top thingy and a bit more work on headphones. [tidusjar] - -- Added some tests and fixed the issue where the DB would get created in the wrong place depending on how you launched the application. [tidusjar] - -- Added the settings page for #32. [tidusjar] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Some final tweaks for #32. [tidusjar] - -- Fixed a bug where if we are the admin we didn't add the request to the db. [tidusjar] - -- Fixed an issue where we would add the Sickrage series but it would fail on adding the seasons. [tidusjar] - -- Properly account for future/past dates when humanizing with moment. [Drewster727] - -- Properly display release date on requests page. [Drewster727] - -- Add missing reference for release mode. [Drewster727] - -- #139 remove dependency and usage of humanize() - should help with cross-platform issues. start using moment.js. [Drewster727] - -- Fix selectors for music list on request page to get sorting working. [Drewster727] - -- Fixed the error #32. [tidusjar] - -- Fixed the logs page. [tidusjar] - -- Another attempt at filtering #32. [tidusjar] - -- A bit more error handling #32. [tidusjar] - -- Improved the availabilty check to include music results #32. [tidusjar] - -- Small changes for #32. [tidusjar] - -- A bit more logging for #32. [tidusjar] - -- More headphones #32 I am starting to hate headphones... Sometimes the artists and albums just randomly fail. [tidusjar] - -- #134 temporary workaround for this. [Drewster727] - -- Task.run for startup caching + fix admin module unit test failures. [Drewster727] - -- Cache injection, error handling and logging on startup, etc. [Drewster727] - -- Tweaks for #32. [tidusjar] - -- #132 auto-approve for admins. [Drewster727] - -- Finished the bulk work for Headphones. Needs testing #32. [tidusjar] - -- Made the album search 10x faster. We are now loading the images in a seperate call. #32. [tidusjar] - -- Add a reference to API Interfaces to fix the build. [tidusjar] - -- #114 start caching quality profiles. Set the cache on startup and when obtaining quality profiles in settings. [Drewster727] - -- Work for #32. [tidusjar] - -- #114 first pass at choosing quality profile when approving + focus search input by default and when switching tabs. [Drewster727] - -- #131 fix for default selected tab. [Drewster727] - -- Remove references to obsolete RequestedBy property + start setting the db schema to the app version, and check that in the future for migrations. [Drewster727] - -- Fixed async issue. [Shannon Barrett] - -- Updating SickRage api to verify Season List is up to date. [Shannon Barrett] - -- Work on showing the requests for #32. [tidusjar] - -- Got the search finished up for #32. [tidusjar] - -- Remove test/temp code in UserLoginModule. [Drewster727] - -- A bit more work on #32 started working on requesting it. The DB is a bit of an issue... [tidusjar] - -- Most of the UI work done for #32. [tidusjar] - -- Basic search working for #32. [tidusjar] - -- Mono datetime offset workaround. [Drewster727] - -- #122 store utc time in the databse + obtain timezone offset of the client upon login + offset times returned to client based on session offset. [Drewster727] - -- Method reference bug fix. [Drewster727] - -- Fix search focus z-index issue (hid suggestions options) [Drewster727] - -- Minor search UI adjustments. [Drewster727] - -- #55 first attempt at "suggestions" starting with "Comming Soon" and "In Theaters" [Drewster727] - -- #106 rename sorting options and polish the dropdown UI a bit. [Drewster727] - -- Started adding the api part for headphones #32. [tidusjar] - -- Upped the time of #123. [tidusjar] - -- First attempt at #123. [tidusjar] - -- We now do not show the text Requested By to the user, we also show a 'success' message instead of a warning when something has already been requested. [tidusjar] - -- Show a "no requests yet" message on the requests page (for each cateogory) [Drewster727] - -- Ignore items that are already available when approving in bulk, and simplify the checking + compile css. [Drewster727] - -- Add a better way to merge RequestedBy and RequestedUsers to avoid code duplication and simplify checks. [Drewster727] - -- Don't query the session as much in the modules, rely on a variable from the base class and store the username as needed. [Drewster727] - -- Show the requested by user from legacy request models. [Drewster727] - -- Only show requested by users to admins + start maintaining a list of users with each request. [Drewster727] - -- #96 fix up notification test feature. [Drewster727] - -- Fix the request page sort/approve button alignment. [Drewster727] - -- When pulling requests, set each to approved that is already available (so the UI avoids showing the approve option for already available content) [Drewster727] - -- Mono doesn't seem to have Tls1.2. Let's try TLS 1 #119. [tidusjar] - -- Specify a protocol type of TLS12. Looks like CP doesn't seem to like SSL3 (it is quite old now so understandable) #119. [tidusjar] - -- Made #85 better. [tidusjar] - -- Fixed the tests. [tidusjar] - -- Made the feedback from Sonarr better when Sonarr already has the series #85. [tidusjar] - -- An attempt to fix #108. [tidusjar] - -- Add some "no results" feedback to the searching + minor UI improvements. [Drewster727] - -- Fix notification tests. [Drewster727] - -- UI - increase icon size of nav menu (they were too small before) [Drewster727] - -- #96 Finished adding test functionality to notifications. [Drewster727] - -- #96 add the necessary back-end code to produce a test message for all notification types (still have to add the test buttons for pushbullet/pushover) [Drewster727] - -- #96 modify notifications interface/service to accept a non-type specific settings object. [Drewster727] - -- #96 Email notification test button (others to come) [Drewster727] - -- Minor UI adjustments. [Drewster727] - -- #84 provide an option in settings to resttrict users from viewing requests other than their own. [Drewster727] - -- #54 comma separated list of users who don't require approval + fix a couple request messages (include show title) [Drewster727] - -- Clean up the sorting option names. add a way to see which filter/sort is currently applied. [Drewster727] - -- Fix up the animations. seems to be related to the data-bound attribute causing the animtions not to fire on each .mix object. [Drewster727] - -- Move approve buttons to the tab content. [Drewster727] - -- Allow approving all requests by category. [Drewster727] - -- Fix up sorting on the request page. [Drewster727] - -- Add ubuntu/debian instructions. [Drewster727] - -- #86 - display movie/show title + year in request notifications. [Drewster727] - -- Show the movie/show title when requesting. [Drewster727] - - -## v1.5.2 (2016-03-26) - -### **Fixes** - -- Stoped users from spamming the request button. [tidusjar] - -- Fixed the logger no longer writing to the file. [tidusjar] - -- Fixed #97. [tidusjar] - - -## v1.5.1 (2016-03-26) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added logs to the sidebar. I'm an idiot. [tidusjar] - -### **Fixes** - -- Approve tv shows or movies. [Drewster727] - -- Fixed a bug where if you had auto approve it wouldn't notify you. [tidusjar] - - -## v1.5.0 (2016-03-25) - -### **New Features** - -- Updated version number for release. [tidusjar] - -- Updated the logic for handling specific seasons in Sonarr and Sickrage. [Shannon Barrett] - -- Updated the readme and added some icons to the navbar. [tidusjar] - -- Added the ability to sepcify a username in the email notification settings for external MTA's. We have had to add a new option called Email Sender because of this. #78. [tidusjar] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added a notification model to the notifiers. Added the backend work for sending a notification for an issue report #75. [tidusjar] - -- Added a subdir to CP, SickRage, Sonarr and Plex #43. [tidusjar] - -### **Fixes** - -- And again. [tidusjar] - -- Made the check actually work. [tidusjar] - -- Finished up #68 and #62. [tidusjar] - -- Finished styling on the logger for now. #59. [tidusjar] - -- Fixed #69. [tidusjar] - -- Working on getting the Sonarr component to work correctly. [Shannon Barrett] - -- Fixes issue #62. [Shannon Barrett] - -- Refactored the Notification service to how it should have really been done in the first place. [tidusjar] - -- Fixed the build. [tidusjar] - -- Finished #49. [tidusjar] - -- Finished #57. [tidusjar] - -- Small changes around the filtering. [tidusjar] - -- Finished adding pushover support. #44. [tidusjar] - -- Resolved #75. [tidusjar] - -- Include DB changes. [tidusjar] - -- Done most on #59. [tidusjar] - -- Lowercase logs folder, because you know, linux. #59. [tidusjar] - -- Adding the imdb when requesting. [tidusjar] - -- Fixed an issue where the table didn't match the model. [tidusjar] - -- Improved the status page with the suggestion from #29. [tidusjar] - -- Hooked up most of #49 Just the validation messages need to be done. [tidusjar] - -- Fixed #74 and #64. [tidusjar] - -- Resolved #70. [tidusjar] - -- Finished #71. [tidusjar] - -- Got the filter working on both movie and tv #57. [tidusjar] - -- Started #57, currently there is a bug where the TV list won't filter. [tidusjar] - - -## v1.4.1 (2016-03-20) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update AvailabilityUpdateService.cs. [Jamie] - - -## v1.4.0 (2016-03-19) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Updated the build version ready for the next release. [tidusjar] - -- Added the api and settings page for Sickrage. Just need to do the tester and hook it up #40. [tidusjar] - -- Added the option to set a CP quality #38. [tidusjar] - -- Added the code to lookup the old requests and refresh them with new information from TVMaze. [tidusjar] - -- Update StatusCheckerTests.cs. [Jamie] - -- Update README.md. [Jamie] - -- Added TVMaze to the search. #21. [tidusjar] - -- Added migration code and cleaned up the DB. [tidusjar] - -- Updated the way we add requests. [tidusjar] - -- Updated the Dapper.Contrib package, it had a bug where it wasn't returning the correct Id from inserts. [tidusjar] - -### **Fixes** - -- This fixes #36. [tidusjar] - -- Should fix issue #36. [Shannon Barrett] - -- When we do a batch update we need to reset the cache. [tidusjar] - -- Fixed an issue where the default quality on Sickrage wouldn't work. [tidusjar] - -- Wow, that was a lot of work. - So, I have now finished #40. - Fixed a bug where we was not choosing the correct tv series (Because of TVMaze) - Fixed a bug when checking for plex titles - Fixed a bug where the wrong issue would clean on the UI (DB was correct) - Refactored how we send tv shows - And too many small changes to count. [tidusjar] - -- Fixed the new dependancy with the admin class tests. [tidusjar] - -- Back to what it was :( [tidusjar] - -- Another test for #37. [tidusjar] - -- This should fix #37. [Jamie Rees] - -- Catch the missing table exception when they have a new DB. [Jamie Rees] - -- Exploratory test for #37. [Jamie Rees] - -- Fixed #33 we now have SSL options for Sonarr and CP. [Jamie Rees] - -- Removed all the html from the new TVMaze api (for overview). Added tests to cover the html removal. updated Readme to remove TheTVDB. [Jamie Rees] - -- Fixed tests. [Jamie Rees] - -- Almost fully integrated TVMaze #21 and also improved the fix for #31. [Jamie Rees] - -- Should fix #28. [Shannon Barrett] - -- Fixed #16 and #30. [tidusjar] - -- Modified the adding of request to update the model with the added ID. [tidusjar] - -- Switched over to the new service. [tidusjar] - -- Fixed #25. [Jamie Rees] - - -## v1.3.0 (2016-03-17) - -### **New Features** - -- Added pushbullet to the sidebar. [Jamie Rees] - -- Updated build version for the next release. [Jamie Rees] - -- Updated readme link. [tidusjar] - -- Added ignore to static tests. [tidusjar] - -- Added Pushbullet notifications #8. [tidusjar] - -- Added first implimentation of the Notification Service #8 Added tests to cover the notification service. [tidusjar] - -- Added validation to the Email settings, also increased the availability checker from 2 minutes to 5. [tidusjar] - -### **Fixes** - -- Fixed #22. [Jamie Rees] - -- Started on #16, nothing is hooked up yet. [tidusjar] - -- Fixed tests. [tidusjar] - - -## v1.2.1 (2016-03-16) - -### **New Features** - -- Update Program.cs. [Jamie] - -- Update Program.cs. [Jamie] - -- Added back the reference. [tidusjar] - -### **Fixes** - -- Removed the email notification settings from the settings (for release 1.2.1) [Jamie Rees] - -- Fixed. [Jamie Rees] - -- Resolved #10. [tidusjar] - - -## v1.2.0 (2016-03-15) - -### **New Features** - -- Updated. [Jamie Rees] - -- Updated appveyor. [Jamie Rees] - -- Update appveyor.yml. [Jamie] - -- Added latest version code and view. Need to finish the view #11. [tidusjar] - -- Added test button to Plex. That's fixed #9. [tidusjar] - -- Added test sonarr button #9. [tidusjar] - -- Added more tests. [tidusjar] - -- Added a bunch of logging. [tidusjar] - -- Added the application tester for CP #9. [tidusjar] - -- Added settings page for #8. [tidusjar] - -- Added pace.js. [tidusjar] - -### **Fixes** - -- Finished the notes! Resolved #7. [Jamie Rees] - -- #12. [Jamie Rees] - -- #12. [Jamie Rees] - -- Finished the status page #11 and some more work to #12. [Jamie Rees] - -- Resolved #7. [tidusjar] - -- Small changes. [tidusjar] - -- Yeah... [tidusjar] - -- Fixed #5 and also added some tests to the availability checker. [tidusjar] - -- Started added tests. [Jamie Rees] - -- Fixed an issue where the issues text appears larger. [Jamie Rees] - - -## v1.1 (2016-03-13) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Updated readme. [Jamie Rees] - -- Added the support for TV Series integrating with Sonarr. [Jamie Rees] - -- Added the functionality to pass a port through an argument. [tidusjar] - -- Added the code to get the quality profiles from Sonarr Started plugging that into the UI. [Jamie Rees] - -- Added the spinners #3. [tidusjar] - -- Added the functionality for the admin to clear the issues. [tidusjar] - -- Added the issues to the requests page. [tidusjar] - -- Added user logout method and unit tests to cover it. [tidusjar] - -- Added DeniedUsers to the view. [tidusjar] - -- Added the denied user check to the UserLoginModule. added a test case to cover it. [tidusjar] - -- Added a missing reference. [tidusjar] - -- Added first real test. [tidusjar] - -- Update README.md. [Jamie] - -- Added the latest version of nuget. [tidusjar] - -- Added travisyml. [tidusjar] - -- Added logging. [tidusjar] - -- Added missing files. [tidusjar] - -- Update README.md. [Jamie] - -- Added logging (Still WIP) [tidusjar] - -- Added favicon and also structured the HTML correctly. [tidusjar] - -- Updated the packages so everything is now with the correct framework (4.5.2) [tidusjar] - -- Added in deletion of requests. [tidusjar] - -- Added test code. [tidusjar] - -- Added dashboard. [tidusjar] - -- Added couchpotato page. [Jamie Rees] - -- Added readme to the project and updated it. [Jamie Rees] - -- Added helpers. [tidusjar] - -### **Fixes** - -- Bug fix, Couchpotato settings wouldn't show in release due to a Nancy bug. [Jamie Rees] - -- Small changes. [Jamie Rees] - -- First release, build 1.0.0. [Jamie Rees] - -- Removed the request limit since it's not currently being used. [Jamie Rees] - -- REmoved Sickbeared for the first release. [Jamie Rees] - -- Fixed #4 We now can manually set the status of a request. [tidusjar] - -- Made the pass in the port a bit more robust. [tidusjar] - -- Styling, Added the functionality for the Sonarr Profiles on the Admin page #2 resolved. [tidusjar] - -- Fixed a bug in the Login and added a unit test to cover that. Added a button to approve an individual request. Fixed some minor bugs in the request screen. [Jamie Rees] - -- Fixed the 'responsive' issue for the search and requests pages #3. [tidusjar] - -- Styling! #3. [tidusjar] - -- Navbar category now will follow you to various screens #3. [tidusjar] - -- Fixed bugs with the 'other' reporting issue and also the clear issues. [tidusjar] - -- We now are appending the users name to who wrote the comment. Rather than it being unknown. [tidusjar] - -- More work on submitting issues. [tidusjar] - -- More test changes. [tidusjar] - -- More tests to cover the login. [tidusjar] - -- Refactoring. [tidusjar] - -- Implimented the password part and authentication with Plex. [tidusjar] - -- Initial Use authentication is working. Need to do the password bit. [tidusjar] - -- Some error handling and ensure we are an admin to delete requests. [tidusjar] - -- Fixed the issue where the Release build would not show the admin screens! [tidusjar] - -- Fixes. [tidusjar] - -- Removed the DI part of the service. TinyIOC doesn't want to work with FluentScheduler. [tidusjar] - -- First pass at the plex update service. [tidusjar] - -- Small changes. [Jamie Rees] - -- Started to impliment the Plex checker. This will check plex every x minutes to see if there is any new content and then update the avalibility of the requests. [Jamie Rees] - -- Mre work. [Jamie Rees] - -- Few small changes, added plex settings. [Jamie Rees] - -- Making the configuration actually do something. Setting a default configuration if there is no DB. [Jamie Rees] - -- Remove post build. [Jamie Rees] - -- Small changes. [Jamie Rees] - -- MOre work. [Jamie Rees] - -- Fixed the issue when sending movies to CouchPotato. [Jamie Rees] - -- Add appveyor. [tidusjar] - -- Build it on 4.5. [tidusjar] - -- Upgraded .net to 4.6. [tidusjar] - -- Typo2. [tidusjar] - -- Typo. [tidusjar] - -- Another update. [tidusjar] - -- Fixed. [tidusjar] - -- More logging to figure out why the we cannot access the admin module in a release build. [tidusjar] - -- Firstpass integrating with CouchPotato. [tidusjar] - -- Some styling. [tidusjar] - -- Fixed the plex friends. Added some unit tests, moved the plex auth into it's own page. [tidusjar] - -- Fully switched the TV shows over to use the other provider. [Jamie Rees] - -- Renamed folders. [tidusjar] - -- Assembly updates. [tidusjar] - -- Moved the rest of the projects. [tidusjar] - -- Moved UI. [tidusjar] - -- Mass rename. [tidusjar] - -- Quick changes. [tidusjar] - -- Started switching the TV over to the new provider (TheTVDB). Currently TV search is partially broken. It will search but we are not mapping all of the details. [tidusjar] - -- Implimented the new TV show Provider (needed for Sonarr TheTvDB) [tidusjar] - -- Started the user auth. [tidusjar] - -- Some work on the requests page. [tidusjar] - -- Made the 'requested' better and made the remove look nicer. [tidusjar] - -- Cleaned up the program a tiny bit. [tidusjar] - -- Removed additional namespace. [tidusjar] - -- Fixed some db issues and added a preview. [Jamie Rees] - -- More work on the settings. [Jamie Rees] - -- Upgraded Json.Net and Nancy packages. [Jamie Rees] - -- Plex friends api. [Jamie Rees] - -- Enabled trace logs. [tidusjar] - -- Sql syntax issue fixed. [tidusjar] - -- Fixed release build. [tidusjar] - -- Small updates including assembly version. [tidusjar] - -- Work on the requests page mostly done. [tidusjar] - -- Work on the TV request. the `latest` parameter is not being passed into the requestTvshow. [tidusjar] - -- Missing file. [tidusjar] - -- Using the IoC container now. [tidusjar] - -- Some plex work. [Jamie Rees] - -- More work. [Jamie Rees] - -- Removed the setup code out of the startup, since we attemtp to connect to the DB before that. [Jamie Rees] - -- Some more work. Need to stop the form submitting on a request. [tidusjar] - -- Moved everything up a directory. [tidusjar] - -- Lots of work! [tidusjar] - -- Done most of the movie search work. [Jamie Rees] - -- First pass with RequestPlex. [tidusjar] - -- Initial commit. [Jamie] - - From cd2011c6ccfa3256942a26973ad458aa54711096 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 7 Apr 2018 00:45:26 +0100 Subject: [PATCH 015/495] !wip fixed lint errors --- .../app/settings/notifications/newsletter.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts index fda23208f..7154543fc 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts @@ -57,15 +57,15 @@ export class NewsletterComponent implements OnInit { if(match && match.length > 0) { this.settings.externalEmails.push(this.emailToAdd); this.emailToAdd = ""; - } else{ - this.notificationService.error("Please enter a valid email address") + } else { + this.notificationService.error("Please enter a valid email address"); } } } public deleteEmail(email: string) { - var index = this.settings.externalEmails.indexOf(email); // <-- Not supported in Date: Mon, 9 Apr 2018 12:43:08 +0100 Subject: [PATCH 016/495] Turn off Server GC to hopefully help with #2127 --- src/Ombi/Ombi.csproj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 3b7e7400e..f5e523db7 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -10,7 +10,9 @@ $(FullVer) - + + false + bin\Debug\netcoreapp2.0\Swagger.xml 1701;1702;1705;1591; From 7f12ab5e1436a6d953df0d19a51a9d1e9d967fa4 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Tue, 10 Apr 2018 13:09:07 +0100 Subject: [PATCH 017/495] !wip changelog --- CHANGELOG.md | 3612 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3612 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63cc385a1..8bfe7e633 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ - Made some improvements to the Sonarr Sync job #2127. [Jamie] +- Turn off Server GC to hopefully help with #2127. [Jamie Rees] + - Fixed #2109. [Jamie] - Fixed #2101. [Jamie] @@ -787,3 +789,3613 @@ - Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] +- Fix non-admin rights (#1820) [Rob Gökemeijer] + +- Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] + +- Add the Issue Reporting functionality (#1811) [Jamie] + +- Removed the forum. [tidusjar] + +- #1659 Made the option to ignore notifcations for auto approve. [Jamie] + +- New Crowdin translations (#1806) [Jamie] + +- Fixed a launch issue. [Jamie] + +- Allow users to login without a password. [Jamie] + +- Fixed the emby notifications not being sent. [Jamie] + +- #1802 and other small fixes. [tidusjar] + +- So... This sickrage thing should work now. [tidusjar] + +- Fixed emby connect login issue. [tidusjar] + +- Stop making unnecessary calls to the update service. [Jamie] + +- Fixed a bug where it blocked users with 0 limits. [Jamie] + +- Done #1788. [tidusjar] + +- More logging. [Jamie] + +- Fixed #1738. [Jamie] + +- Fixed build. [Jamie] + +- Fixed the issue where notifications were not sendind unless we restarted #1732. [tidusjar] + +- Fixed an issue with a trailing space in the subdir. [tidusjar] + +- Fixed #1774. [Jamie] + +- #1773. [Jamie] + +- Roll back rxjs (#1778) [bazhip] + +- Fixed build. [Jamie] + +- Fixed #1763. [Jamie] + +- Fix "content length error" on preview gif (#1768) [OoGuru] + +- New preview gif for Ombi V3 README (#1767) [OoGuru] + +- Remove debug code. [tidusjar] + +- Fix #1762. [tidusjar] + +- Fixed the preset themes not loading. [tidusjar] + +- Fixed #1760 and improvements on the auto updater. We may now support windows services... #1460. [Jamie] + +- Fixed #1754. [Jamie] + +- Hide the subject when it's not being used. [Jamie] + +- Error handling #1749. [Jamie] + +- New Crowdin translations (#1741) [Jamie] + +- #1732 #1722 #1711. [Jamie] + +- Fixed an issue with switching the preset themes. [Jamie] + +- Fixed #1743. [Jamie] + +- Fixed #1742. [tidusjar] + +- Fix #1742. [tidusjar] + +- Fixed landing page. [Jamie] + +- Fixed. [Jamie] + +- Translated the Requests page and fixed #1740. [Jamie] + +- Fix crash. [Jamie] + +- Sickrage done. Ish... So i've written all the code by looking at the API. the key there is i've looked at the api. I have not tested anything so expect this to fail. [Jamie] + +- SickRage settings UI. [Jamie] + +- Fixed #1721. [tidusjar] + +- Fixed the preset themes issue. [tidusjar] + +- New Crowdin translations (#1654) [Jamie] + +- Fix build. [Jamie] + +- #1460. [Jamie] + +- Fixed tests. [Jamie] + +- Return css as MIME text/css. [Jamie] + +- More added for the preset themes. [Jamie] + +- Moved around the custom styles. [Jamie] + +- More renames. [Jamie] + +- Renames. [Jamie] + +- Load the first 100 requests. [Jamie] + +- Reduce the memory consumption #1720. [Jamie] + +- Moved the schedules jobs into it's own database, see if it helps with the db locking #1720. [Jamie] + +- Fixed #1712. [tidusjar] + +- Potential fix for #1702. [tidusjar] + +- Fixed #1708. [tidusjar] + +- Fixed #1677. [tidusjar] + +- Fixed build. [tidusjar] + +- Potential fix for the DB locking issue #1720. [tidusjar] + +- #1698. [Jamie] + +- Fixed #1705. [tidusjar] + +- Fixed #1703. [tidusjar] + +- Finished adding preset themes. [Jamie] + +- Fixed #17000. [Jamie] + +- Remove the themes because waiting for a merge from lerams project. [Jamie] + +- Finsihed adding preset themes. [Jamie] + +- Fixed #1677. [Jamie] + +- Temp fix for #1683. [Jamie] + +- Fixed #1685. [Jamie] + +- Lossless Compression of images saves 83 KB (#1676) [Fish2] + +- Fixed the availability checker. [tidusjar] + +- Fixed build. [tidusjar] + +- Push out missing migration. [tidusjar] + +- Potential fix for #1674. [tidusjar] + +- Fixed an issue with the caching. [tidusjar] + +- Fixed telegram #1667. [tidusjar] + +- Fixed #1663. [tidusjar] + +- Should fix #1663. [tidusjar] + +- Stop logged in users going to the login page. [Jamie] + +- Fixed it not updating. Styles should be good now. [Jamie] + +- Re did some of the styling on the movie search page, let me know your thoughts. [Jamie] + +- Fixed #1657. [Jamie] + +- Fixed #1655. [Jamie] + +- Removed authentication resul. [Jamie] + +- New Crowdin translations (#1651) [Jamie] + +- New Crowdin translations (#1648) [Jamie] + +- New Crowdin translations (#1638) [Jamie] + +- Fixed #1644. [Jamie] + +- Moar logs #1643. [tidusjar] + +- Fixed #1640. [tidusjar] + +- Fixed the null ref exception #1460. [tidusjar] + +- Fixed landing page. [TidusJar] + +- Fixed #1641. [TidusJar] + +- Fixed #1641. [TidusJar] + +- New Crowdin translations (#1635) [Jamie] + +- Fixed #1631 and improved translation support Included startup args for the auto updater #1460 Mark TV requests as available #1632. [tidusjar] + +- Remove 32bit. [Jamie] + +- More 32bit support. [Jamie] + +- We now show "Available" for tv shows that is fully available #1602. [tidusjar] + +- Fixed the issue where we have got an episode but not the related series. #1620. [tidusjar] + +- Fixed the dropdown not working on iOS in the settings #1615. [tidusjar] + +- Fixed sonarr not monitoring the latest season #1534. [tidusjar] + +- Fixed the issue with firefox #1544. [tidusjar] + +- Fixed discord #1623. [tidusjar] + +- Add browserstack thanks (#1627) [Matt Jeanes] + +- Fix the exception #1613. [Jamie] + +- Found where we potentially are setting a new poster path, looks like the entity was being modified and being set as Tracked by entity framework, so the next time we called SaveChangesAsync() it would save the new posterpath on the entity. [Jamie] + +- Small modifications. [Jamie] + +- Fixed #1622. [Jamie] + +- Various improvements to webpack/gulp/vscode support (#1617) [Matt Jeanes] + +- Episodes in requests are now in order #1597 (#1614) [masterhuck] + +- Fixed a null reference issue in the Plex Content Cacher. [Jamie.Rees] + +- Fixed #1610. [tidusjar] + +- Really fixed the build this time. [tidusjar] + +- Fixed build. [tidusjar] + +- Made the updater work again #1460. [tidusjar] + +- Adding logging into the auto updater and also added more logging around the create inital user for #1604. [tidusjar] + +- Fixed the issue where we did not check if they are already in sonarr when choosing certain options #1540. [tidusjar] + +- We can now delete tv child requests and the parent will get remove #1603. [tidusjar] + +- Finished the api changes requested #1601. [tidusjar] + +- Fixed the Hangfire server timeout issue #1605. [tidusjar] + +- Fixed notifications not sending #1594. [tidusjar] + +- Fixed #1583 you can now delete users. Fixed the issue where the requested by was not showing. Finally fixed the broken poster paths. [tidusjar] + +- Fixed the issue where movie requests were no longer being requested. [tidusjar] + +- Started adding some more unit tests #1596. [Jamie.Rees] + +- #1588 When we make changes to any requests that we can trigger a notification, always send it to all notification agents, even if the user wont recieve it. [Jamie.Rees] + +- Add a message when email notifications are not setup when requesting a password reset. #1590. [Jamie.Rees] + +- Removed text that we no longer need. [Jamie.Rees] + +- Fixed #1574. [Jamie.Rees] + +- #1460 looks like the permissions issue has been resolved. Just need to make sure the Ombi process is terminated. [Jamie.Rees] + +- Put back the old download code. [Jamie.Rees] + +- Test. [Jamie] + +- Build sln. [Jamie.Rees] + +- Order by the username #1581. [Jamie.Rees] + +- Remove sonarr episodes from the cache table. [Jamie.Rees] + +- Couchpotato finished. [tidusjar] + +- Disable run import button if no import options are selected. [tidusjar] + +- Fixed #1574. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixes the issue with non windows systems unable to unzip the tarball #1460. [tidusjar] + +- Finished the couchpotato settings. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixed #1570 #1571. [tidusjar] + +- Fixed #1547. [tidusjar] + +- Should fix #1538. [tidusjar] + +- Fixed #1553. [tidusjar] + +- Fixed #1546. [tidusjar] + +- Fixed #1543. [tidusjar] + +- Fixes an issue with Movie caching not working on develop branch of Radarr (#1567) [Jeffrey Peters] + +- This adds two fields to the Email Notifications settings page. It allows for the disabling of TLS/SSL as well as the ability to disable certificate validation when sending notification emails. (#1552) [Jeffrey Peters] + +- Fixed typo (#1551) [Codehhh] + +- Use Sqlite storage for Hangfire. [tidusjar] + +- Fixed the overrides #1539 also display it on screen now too. [tidusjar] + +- Fixed #1542 also added VSCode support. [tidusjar] + +- Fixed some cosmetic issues #865. [Jamie.Rees] + +- Fixed #1531. [Jamie.Rees] + +- Small fixes #865. [Jamie.Rees] + +- Some errors fixed and some ui improvements #865. [tidusjar] + +- Auto-scale large images down to container size (#1529) [Avi] + +- Fix logo on login page. (#1528) [Avi] + +- Another potential issue? :/ [tidusjar] + +- Real fix. [tidusjar] + +- #1513 Added storage path. [Jamie.Rees] + +- Fixed the discord issue relating to images #1513. [Jamie.Rees] + +- Fixed the issue sending movies to Radarr #1513 Fixed typo #1524. [Jamie.Rees] + +- Fixed logo on reset password pages fixed the run importer button on the user management settings. [Jamie.Rees] + +- Fixed crash/error #865. [tidusjar] + +- #1513 fixed the landing page and also the reverse proxy images. [tidusjar] + +- #1513 correctly set the child requests as approved. [tidusjar] + +- Fixed an issue that potentially causes as issue when siging into plex #865. [tidusjar] + +- Remove dev branch. [PotatoQuality] + +- Prepare readme for upcoming beta. [PotatoQuality] + +- #1513 partially fixed a bug. [tidusjar] + +- Fixed the exception. [tidusjar] + +- Fixed the application url not saving #1513. [tidusjar] + +- Fixed liniting. [tidusjar] + +- REVERSE PROXY BITCH! #1513. [tidusjar] + +- Fixed a bug where we were marking the wrong episodes as available #1513 #865. [Jamie.Rees] + +- Fixed an issue where we messed up the pages and routing. [Jamie.Rees] + +- Emby user importer is now therer! #1456. [tidusjar] + +- #1513 Added the update available icon. [tidusjar] + +- Fixed the issue of it showing as not requested when we find it in Radarr. Made the tv shows match a bit more to the movie requests Added the ability for plex and emby users to login Improved the welcome email, will only show for users that have not logged in Fixed discord notifications the about screen now checks if there is an update ready #1513. [tidusjar] + +- Support email addresses as usernames #1513. [Jamie.Rees] + +- Link to issue treath. [PotatoQuality] + +- Give correct feedback when testing email notifications #1513. [Jamie.Rees] + +- Report issue removed and the deny dropdown removed #1513. [Jamie.Rees] + +- #1513 removed the discord text when testing pushbullet. [Jamie.Rees] + +- Made a lot of changes around the notifcations to support the custom app name also started on the welcome email ##1456. [Jamie.Rees] + +- Fixed the bug where we were displaying shows where we do not have enough information to request #1513. [Jamie.Rees] + +- #1513 added the network to tv shows. [Jamie.Rees] + +- Fixed the whitespace issue #1513. [Jamie.Rees] + +- Fixed the swagger endpoint #865 #1513 Fixed the custom image issue on the login page Fixed the bug when clicking on the tab on the requests page it would switch to the wrong one Swagger is now back @ /swagger. [tidusjar] + +- Optimized images, Update old compressed image with a new lossless one. (#1514) [camjac251] + +- #1513 #865 Fixed the issue where we do not send the requests to Radarr/Sonarr when approving. [tidusjar] + +- #1506 #865 Fixed an issue with the test buttons not working correctly. [tidusjar] + +- #865 Added donation link. [tidusjar] + +- Fixed a bunch of issues on #1513. [tidusjar] + +- #1460 Added the Updater, it all seems to be working correctly. #865. [Jamie.Rees] + +- Removed percentage. [Jamie.Rees] + +- Fixed linter. [Jamie.Rees] + +- Fixed some bugs in the UI #865. [Jamie.Rees] + +- Improved the search buttons #865. [Jamie.Rees] + +- More logging #865. [Jamie.Rees] + +- Made build faster. [Jamie.Rees] + +- More logging. [Jamie.Rees] + +- Set debug level to Debug for now. [Jamie.Rees] + +- Add linting and indexes for interfaces/services (#1510) [Matt Jeanes] + +- Fixed the issue with the tv search not working #1463. [Jamie.Rees] + +- Latest practices... also probably broke some styles - sorry (#1508) [Matt Jeanes] + +- Build with the branch version. [tidusjar] + +- Build fix. [tidusjar] + +- Fixed build. [tidusjar] + +- Omgwtf so many changes. #865. [tidusjar] + +- Tests. [Jamie.Rees] + +- #1456 Started on the User Importer Also added the remember me button. [Jamie.Rees] + +- Made some UI changes, reworked the Emby and Plex screens to make them more user friendly and no so fugly. #865 Also made the login page placeholder text slightly lighter. [Jamie.Rees] + +- Cake skip verification build stuff #865. [Jamie.Rees] + +- Some fixes around the UI and managing requests #865. [tidusjar] + +- #1486. [Jamie.Rees] + +- #1486. [Jamie.Rees] + +- Upgraded to .net core 2.0 #1486. [Jamie.Rees] + +- #865 Finished the landing page, we now check the server's status. [Jamie.Rees] + +- Fixed build. [TidusJar] + +- Removed the telegram api. [Jamie.Rees] + +- Small changes on the updater #1460 #865. [Jamie.Rees] + +- Remove unused functions. [Dhruv Bhavsar] + +- Make Episode picker similar to Requests Child view. #1457 #1463. [Dhruv Bhavsar] + +- Fix merge conflict for TvRequests component. [Dhruv Bhavsar] + +- Upstream Changes... [Dhruv Bhavsar] + +- Clean up Requests page code by moving children request to old component, remove additional REST calls when merging and update component names to make more sense. [Dhruv Bhavsar] + +- Lots of different UI enhancements and fixes #865. [tidusjar] + +- Gitchangelog. [tidusjar] + +- Fixed the issue where we were using the wrong availability options. [tidusjar] + +- Fixed a bunch of bugs in Ombi #865. [tidusjar] + +- Build versioning. [Jamie.Rees] + +- #1460 The assembly versioning seems to work correctly now. [Jamie.Rees] + +- More build versioning changes #865. [tidusjar] + +- Fixed cake script. [Jamie.Rees] + +- WIP on the build versioning for the Updater #1460 #865. [Jamie.Rees] + +- Versioning. [Jamie.Rees] + +- Package versions. [Jamie.Rees] + +- #1460 #865 working on the auto updater. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Small changes around the roles #865. [tidusjar] + +- Improvements to the UI and also finished the availability checker #865 #1464. [Jamie.Rees] + +- Availability Checker #1464 #865. [Jamie.Rees] + +- Fixed ##1492 and finished the episode searcher for #1464. [Jamie.Rees] + +- #1464. [tidusjar] + +- Reload the settings #1464 #865. [Jamie.Rees] + +- #1464 added the Plex episode cacher #865. [Jamie.Rees] + +- Fixed some issues around the tv requests area Added mattermost and telegram notifications #1459 #865 #1457. [tidusjar] + +- Fix global.json. [Dhruv Bhavsar] + +- Working UI for Requests. Approval/Deny does not work as it doesn't in your code either. [Dhruv Bhavsar] + +- Enable diagnostic on build #865. [Jamie.Rees] + +- Fixed the user token issue #865. [Jamie.Rees] + +- Some small refresh token work #865. [Jamie.Rees] + +- Initial TV Requests UI rebuild. [Dhruv Bhavsar] + +- Made a start on supporting multiple emby servers, the UI needs rework #865. [Jamie.Rees] + +- #865 #1459 Added the Sender From field for email notifcations. We can now have "Friendly Names" for email notifications. [Jamie.Rees] + +- Redirect to the landing page when enabled #1458 #865. [Jamie.Rees] + +- Removed IdentityServer, it was overkill #865. [Jamie.Rees] + +- Fixed another bug with identity. #865 I'm thinking about removing it. Causing more hassle than it's worth. [tidusjar] + +- #1460 #865. [tidusjar] + +- Delete appveyor_old.yml. [Jamie] + +- Fixed path. [Jamie.Rees] + +- Silent build level. [Jamie.Rees] + +- #1459 Forgot to get the Pushbullet agent to look up the pusbullet templates rather than the Discord ones. Updated the Gitchange log. [Jamie.Rees] + +- Made the placeholder color on the login page a bit lighter #865. [Jamie.Rees] + +- Landing and login page changes #865 #1485. [tidusjar] + +- #1458 #865 More work on landing. [Jamie.Rees] + +- Working on the landing page #1458 #865. [tidusjar] + +- A lot of clean up and added a new Image api #865. [Jamie.Rees] + +- Cleaned up the Logging API slightly #1465 #865. [Jamie.Rees] + +- Fixed the Identity Server discovery bug #1456 #865. [tidusjar] + +- Fixed the issue with the Identity Server running on a different port, we can now use -url #865. [Jamie.Rees] + +- Try again. [TidusJar] + +- Publish ubuntu 16.04. [Jamie.Rees] + +- Chnaged the updater job from Minutely to Hourly. [Jamie.Rees] + +- Some work around the Auto Updater and other small changes #1460 #865. [Jamie.Rees] + +- Missed a file. [tidusjar] + +- Fixed the swagger issue. [tidusjar] + +- RDP issues. [tidusjar] + +- Appveyor build rdp investigation. [tidusjar] + +- Working on the requests page #1457 #865. [tidusjar] + +- Made the password reset email style the same as other email notifications #1456 #865. [Jamie.Rees] + +- Fixed some bugs around the authentication #1456 #865. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Fixed build #1456. [Jamie.Rees] + +- #1456 #865 Started on allowing Plex Users to sign in through the new authentication server. [Jamie.Rees] + +- Removed covalent. [Jamie.Rees] + +- #1456 Reset Password stuff #865. [Jamie.Rees] + +- Finished implimenting Identity with IdentityServer4. #865 #1456. [Jamie.Rees] + +- Moved over to using Identity Server with Asp.Net Core Identity #1456 #865. [Jamie.Rees] + +- Started on the requests rework #865. [Jamie.Rees] + +- Extended the Emby API. [Jamie.Rees] + +- Started reworking the usermanagement page #1456 #865. [tidusjar] + +- Lots of refactoring #865. [Jamie.Rees] + +- Created an individual user api endpoint so we can make the user management pages better #865. [TidusJar] + +- Lot's of refactoring. [Jamie.Rees] + +- #1462 #865 Had to refactor how we use notificaitons. So we now have more notification fields about the request. [Jamie.Rees] + +- Looks like Sonarr is finished and works. A lot simplier this time around. #865. [tidusjar] + +- More work on the Sonarr Api Integration #865. [tidusjar] + +- Started on sonarr #865. [tidusjar] + +- Small changes #865. [tidusjar] + +- Damn son. So many changes... Fixed alot of stuff around tv episodes with the new DB model #865. [tidusjar] + +- Fixed the TV Requests issue #865. [Jamie.Rees] + +- Fixed a load of bugs need to figure out what is wrong with tv requests #865. [tidusjar] + +- #865 rework the backend data. Actually use real models rather than a JSON store. [Jamie.Rees] + +- Fixed the build issue #865. [tidusjar] + +- Allow us to use Emby as a media server. [tidusjar] + +- More Update #865. [Jamie.Rees] + +- Deployment changes. [Jamie.Rees] + +- More work on the Updater. [Jamie.Rees] + +- Lots of fixes. Becoming more stable now. #865. [tidusjar] + +- Small fixes around the searching. [Jamie.Rees] + +- Some rules #865. [Jamie.Rees] + +- Oops. [TidusJar] + +- Started on the Discord API settings page. [TidusJar] + +- Email Notifications are now fully customizable and work! #865. [Jamie.Rees] + +- Small changes and fixed some stylingon the plex page #865. [Jamie.Rees] + +- More on #865 TODO, Find out whats going on with the notifications and why exceptions are being thrown. [Jamie.Rees] + +- Oops. [Jamie.Rees] + +- Ok #865 fixed the published exe. [Jamie.Rees] + +- Fixed errors. [Jamie.Rees] + +- Fixed build script. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Some more #865. [Jamie.Rees] + +- Create appveyor.yml. [Jamie] + +- The Approving child requests now work! [Jamie.Rees] + +- Fixed many bugs #865. [Jamie.Rees] + +- Loads of changes, improved the movie search stylings is back. [Jamie.Rees] + +- Moved to webpack and started on new style. [Jamie.Rees] + +- Fixed the TV search via Trakt not returning Images anymore. #865. [Jamie.Rees] + +- Rules changes and rework. [Jamie.Rees] + +- Request Grid test. [Jamie.Rees] + +- Small cleanup #865. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Started the Radarr Settings #865. [Jamie.Rees] + +- Massive amount of rework on the plex settings page. It's pretty decent now! #865. [tidusjar] + +- Fixed build. [Jamie.Rees] + +- Fixed build. [tidusjar] + +- Rules #865. [tidusjar] + +- Stuff. [Jamie.Rees] + +- Forgot to uncomment. [Jamie.Rees] + +- Tetsd. [Jamie.Rees] + +- Build task changes. [Jamie.Rees] + +- Adsa. [Jamie.Rees] + +- Appveyor. [Jamie.Rees] + +- Stuff around tokens and also builds. [Jamie.Rees] + +- Finished the Plex Content Cacher. Need to do the episodes part but things are now showing as available! #865. [tidusjar] + +- Small user changes #865. [Jamie.Rees] + +- Stuff #865 need to work on the claims correctly. [Jamie.Rees] + +- Reworked the TV model AGAIN #865. [Jamie.Rees] + +- The move! [Jamie.Rees] + +- Fixed build #865. [Jamie.Rees] + +- Fixed the user management #865. [Jamie.Rees] + +- #865 Added support for multiple plex servers. [Jamie.Rees] + +- Bleh. [tidusjar] + +- Small changes. [Jamie.Rees] + +- Fixed the build. [Jamie.Rees] + +- Fixes. [Jamie.Rees] + +- Bundling changes. [Jamie.Rees] + +- Some series information stuff, changes the pace theme too. [Jamie.Rees] + +- Docker support and more, redesign the episodes. [tidusjar] + +- Stuff around episode/season searching/requesting. [Jamie.Rees] + +- Removed redundant folders. [tidusjar] + +- Lots of backend work. [tidusjar] + +- Fixed build. [Jamie.Rees] + +- TV Request stuff. [Jamie.Rees] + +- Work around the user management. [tidusjar] + +- More. [Jamie.Rees] + +- Lots and Lots of work. [Jamie.Rees] + +- Diagnostic changes. [tidusjar] + +- Fixed hangfire exception. [tidusjar] + +- Remove xunit. [tidusjar] + +- Lots more work :( [Jamie.Rees] + +- More changes. [tidusjar] + +- #865. [Jamie.Rees] + +- Small changes. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- More mapping. [Jamie.Rees] + +- Mapping mainly. [Jamie.Rees] + +- Fix systemjs config not being included. [Matt Jeanes] + +- Fixed bundling and various improvements. [Matt Jeanes] + +- Finished the emby wizard #865. [tidusjar] + +- Finished the wizard #865 (For Plex Anyway) [tidusjar] + +- Small changes. [tidusjar] + +- More work on Wizard and Plex API #865. [tidusjar] + +- Settings. [Jamie.Rees] + +- Settings for Ombi. [Jamie.Rees] + +- Fixed some issues around the identity. [Jamie.Rees] + +- #865 more for the authentication. [tidusjar] + +- Auth. [Jamie.Rees] + +- More on the search and requests page. It's almost there for movies. Need to add some filtering logic #865. [tidusjar] + +- #865. [Jamie.Rees] + +- Fixed build. [tidusjar] + +- Messing around with the settings. [tidusjar] + +- Fixed the yml. [Jamie.Rees] + +- Remove unneeded bundle config. [Matt Jeanes] + +- Redo dotnet publish targets. [Jamie.Rees] + +- Bundling changes. [Jamie.Rees] + +- Stuff. [Jamie.Rees] + +- Move app into wwwroot. [Jamie.Rees] + +- Put uglify back in! [Jamie.Rees] + +- Wrong line. [Jamie.Rees] + +- Matt is helping. [Jamie.Rees] + +- Revert. [tidusjar] + +- Small tweaks. [tidusjar] + +- Upgrade to .Net Standard 1.6. [tidusjar] + + +## v2.2.1 (2017-04-09) + +### **New Features** + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added the forums. [tidusjar] + +- Updates. [tidusjar] + +- Update gulpfile.js. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added a retry policy around the emby newsletter. [Jamie.Rees] + +### **Fixes** + +- Revert "Merge branch 'DotNetCore' into dev" [tidusjar] + +- More borken build. [Jamie.Rees] + +- Started adding requesting. [Jamie.Rees] + +- Done the movie searching. [tidusjar] + +- #865. [tidusjar] + +- More. [tidusjar] + +- Moar. [tidusjar] + +- Small changes. [tidusjar] + +- Styling. [Jamie.Rees] + +- MOre changes. [Jamie.Rees] + +- Spacing. [Jamie.Rees] + +- Try again. [Jamie.Rees] + +- More. [Jamie.Rees] + +- Again. [Jamie.Rees] + +- Anbother. [Jamie.Rees] + +- Another. [Jamie.Rees] + +- Another. [Jamie.Rees] + +- Retry. [Jamie.Rees] + +- A. [Jamie.Rees] + +- Fixed. [Jamie.Rees] + +- Cahnge 2. [Jamie.Rees] + +- Appveyor change. [Jamie.Rees] + +- The start of a new world. [Jamie.Rees] + +- Fixed the migration number and order by the added date for the newsletter #1264. [tidusjar] + +- Forgot this change. [tidusjar] + +- Also fixed the issue for the Emby Newsletter where episodes were not getting added :( [tidusjar] + +- #1264 "They may take our lives, but they'll never take our freedom!" [tidusjar] + +- Finished reworking the Sonarr Integration. Seems to be working as expected, faster and most stable. It's Not A Toomah! [tidusjar] + +- Small bit of work. [Jamie.Rees] + +- Made a start on the new Sonarr integration. [tidusjar] + +- For test emails, if there is no new content then just grab some old data. [tidusjar] + +- Fixed an issue where the emby newsletter was always showing series. [tidusjar] + + +## v2.2.0 (2017-03-30) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added a new setting for the Netflix option, we can now disable it appearing in the search. [tidusjar] + +- Update German Translation. [Marius Schiffer] + +- Added a release notes page, you can access via Admin>Updates>Recent Changes tab. Note to self, need to put better comments in for users to understand! [Jamie.Rees] + +- Added gravitar image. [Jamie.Rees] + +- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] + +- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] + +- Added some logging around API calls. [smcpeck] + +- Changed IEmbyAvailabilityChecker to use IEnumberables + checking actor search against Emby content + PR feedback. [smcpeck] + +- Changed actor searching to support non-actors too. [smcpeck] + +- Added a 10 second timer to refresh some new caching I put in. [smcpeck] + +- Added root folder and approving quality profiles in radarr #1065. [tidusjar] + +- Added some debugging code around the newsletter for Emby #1116. [tidusjar] + +- Added a TMDB Rate limiter for the newsletter. [tidusjar] + +- Added port check in wizard. also fixed favicon. [tidusjar] + +- Update Radarr placeholder. [d2dyno] + +- Added the user login for emby users #435. [tidusjar] + +- Added User Management support for Emby #435. [tidusjar] + +- Added emby to the sidebar #435. [tidusjar] + +- Added API endpoint for /actor/new/ to support searching for movies not already available/requested. [smcpeck] + +- Update ISSUE_TEMPLATE.md. [Jamie] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +### **Fixes** + +- Translation changes. [Jamie.Rees] + +- Syntax error. [tidusjar] + +- Fixed an issue where we were retrying the API call when the Plex users login creds were invalid. #1217. [tidusjar] + +- Slightly increased the wait time for the emby newsletter also fixed a potential error in the plex user checker. [Jamie.Rees] + +- Fixed an issue where we were not notifiying emby users. [Jamie.Rees] + +- Fixed the issue where the recent changes page was not showing the correct date. #1296. [Jamie.Rees] + +- Fixed #1252 (Show the correct user type on the management page for Plex Users) [Jamie.Rees] + +- Fixed the casting error #1292. [Jamie.Rees] + +- Fix test newletter not sending when empty. [Dhruv Bhavsar] + +- Quick fix for email false positive message. ISSUE: #1286. [Dhruv Bhavsar] + +- Fixes around the newsletter. We will now correctly show newly added shows and also newly added episodes. #1163. [tidusjar] + +- Fixed a sonarr deseralization error. [tidusjar] + +- Increased the delay for the Episode information api calls. #1163. [tidusjar] + +- Looks like we were overloading emby with out api calls. [tidusjar] + +- Fixed the root path escaping issue for Radarr too! [tidusjar] + +- Some small backend newsletter changes, we can now detect if there are any movies and/or tv shows, if there are none then we will no longer send out an empty newsletter. [Jamie.Rees] + +- Remoddeled the notificaiton settings to make it easier to add more. This is some techinical changes that no one except me will ever notice :( [Jamie.Rees] + +- Fixed #1234. [Jamie.Rees] + +- A fix to the about page and also started to rework the notification backend slightly to easily add more notifications. [Jamie.Rees] + +- Adding more logging into the Plex Cacher. [Jamie.Rees] + +- #1218 changed the text when we cannot display release notes for dev and EAP branches. [Jamie.Rees] + +- Fix for #1236. [SuperPotatoMen] + +- Tooltips. [Jamie.Rees] + +- #236. [Jamie.Rees] + +- #1102. [Jamie.Rees] + +- Done #1012. [Jamie.Rees] + +- Oops #1134. [Jamie.Rees] + +- Fixed #1121. [Jamie.Rees] + +- Fixed #1210. [Jamie.Rees] + +- Fixed typo #1134. [Jamie.Rees] + +- Fixed #1223. [Jamie.Rees] + +- Another newsletter fix attempt #1163 #1116. [tidusjar] + +- Fixup! Reset the branch on v2.1.0 tag to get to a shared state between dev and Master. [distaula] + +- Fixed a bug in the Plex Newsletter. [tidusjar] + +- Typo. [tidusjar] + +- Fixed around the newsletter and a small feature around the permissions/features (#1215) [Jamie] + +- Fixed #1189. [tidusjar] + +- Fixed #1195. [Jamie.Rees] + +- Fixed #1195. [Jamie.Rees] + +- Fixed #1192. [Jamie.Rees] + +- Fixed issue where we could get null rating keys on Plex. [tidusjar] + +- Needed to treat a 201 as success, too. + removed some commented out code. [Shaun McPeck] + +- Normalized spacing/tabs. [smcpeck] + +- Move local user login to be the first thing checked; renamed old Api variable to PlexApi now that Emby is in play. [smcpeck] + +- Remove all the polling/retry logic around HP requests. This was a problem do to not properly awaiting the initial AddArtist API call being sent to HP. Also fix SetAlbumStatus to use ReleaseId instead of MusicBrainsId (same fix previously applied to AddArtist). [smcpeck] + +- Restore checking of HTTP StatusCode on ApiRequests; remove checking of response.ErrorException. [smcpeck] + +- Reverted (for now) non-200 response handling; added some extra logging. [smcpeck] + +- Tweaked ApiRequest behavior on non-200 responses; think it was breaking login. :-" [smcpeck] + +- Only deserialize response payload in ApiRequest when StatusCode == 200. Will a default return value in other cases cause other issues? [smcpeck] + +- Headphones - added releaseID to generic RequestedModel and passing that through to HP request. Their API doesn't request via the MusicBrainzId. [smcpeck] + +- Fixed #1038. [tidusjar] + +- Fixed a slight issue where we could click the change folders button rather than the dropdown arrow #1189. [tidusjar] + +- Bunch of updater files. [tidusjar] + +- #1163 #117. [tidusjar] + +- Removed some unnecessary 'ConfigureAwait` uses. [smcpeck] + +- Remove meaningless html class from actor searching checkbox. [smcpeck] + +- Fixed an issue where we were not always showing movies from external programs. [tidusjar] + +- Remove extra delay when filtering out existing movies. [smcpeck] + +- Post merge build fixes. [smcpeck] + +- Fix. [tidusjar] + +- Fixed #1177. [tidusjar] + +- Fixed #1152. [tidusjar] + +- Fixed #1123. [tidusjar] + +- Fixed a bug when sending to radarr. [tidusjar] + +- Fixed #1133. [tidusjar] + +- Fixed issues img. [Jamie.Rees] + +- Stop Plex being enabled on the first time installing #1048. [Jamie.Rees] + +- The landing page now works for emby #435. [tidusjar] + +- Fixed #1104. [tidusjar] + +- Fixed #1090. [tidusjar] + +- Fixed #1103. [tidusjar] + +- Small changes. [tidusjar] + +- Break out Mass Email feature into its own tab, upgrade Font Awesome and clean up some comments. [dhruvb14] + +- Fix typo. [Travis Bybee] + +- Fixed #1066. [Jamie.Rees] + +- Fixed broken builds. [Jamie.Rees] + +- Fixed #1083. [Jamie.Rees] + +- #1049. [tidusjar] + +- Fixed #1071. [tidusjar] + +- Fixed #1048 #1081. [tidusjar] + +- #1074. [Jamie.Rees] + +- #1069. [Jamie.Rees] + +- Fix for #1068. [tidusjar] + +- Remove duplciate tv show status. [tidusjar] + +- Some request ui changes. [tidusjar] + +- Removed references to Plex. [tidusjar] + +- Removed plex from the scheduled jobs ui. [tidusjar] + +- First run of the newsletter set it to a test. [tidusjar] + +- Reworked the newsletter for Emby! Need to rework it for Plex and use the new way to do it. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixed the mass email, it was only being set to users with the newsletter feature #358. [tidusjar] + +- Removed Plex Request from the notifications. [tidusjar] + +- Finish implementing mass email feature. [dhruvb14] + +- @tidusjar pointed out runtime error!! [dhruvb14] + +- Does not compile, need to get data from UI into nancy somehow and figure out why IMassEmail is not initializing. [dhruvb14] + +- Begin Implementing Mass Email Section. [dhruvb14] + +- Hide the auto update btn #236 Fixed where we were not populating the emby episodes #435. [tidusjar] + +- Fix Radarr labels. [d2dyno] + +- Fixed pace loader. [Jamie.Rees] + +- Check if Emby/Plex is enabled before starting the job. [Jamie.Rees] + +- Fixed #1036. [Jamie.Rees] + +- Fixed a typo and changed wording. [Torkil Liseth] + +- Fixed #1035. [Jamie.Rees] + +- Fix for #1026. [Jamie.Rees] + +- Fixed #1042. [tidusjar] + +- #435. [Jamie.Rees] + +- #435 Started the wizard. [Jamie.Rees] + +- Removed. [tidusjar] + +- Final Fixes. [dhruvb14] + +- Partial fix for broken HR tag's in Email... [dhruvb14] + +- DAMN! #435 that's a lot of code! [tidusjar] + +- Started adding Emby, Lots of backend work done. Need a few more services done and login and user management. #435. [tidusjar] + +- UI changes to add checkbox and support searching for only new matches via new API. [smcpeck] + +- REFACTOR: IAvailabilityChecker - changed arrays to IEnumerables. [smcpeck] + +- UI changes to consume actor searching API. [smcpeck] + +- API changes to allow for searching movies by actor. [smcpeck] + +- Enforcing async/await in synchronous methods that were marked async. [smcpeck] + + +## v2.1.0 (2017-01-31) + +### **New Features** + +- Update README.md. [Jamie] + +- Update .gitattributes. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added the new labels to the search. [tidusjar] + +- Added a switch to use the new search or not, just in case people do not like it. added a migration to turn on the new search. [Jamie.Rees] + +- Added a bunch of categories for tv search similar to what we have for movies. [Jamie.Rees] + +### **Fixes** + +- Fixed typos. [Haries Ramdhani] + +- Fix typo in readme. [tdorsey] + +- Fixed #985. [Jamie.Rees] + +- FIxed #978. [tidusjar] + +- Fixed the approval issue for #939. [tidusjar] + +- Some general improvements. [tidusjar] + +- Turned off migration for now. [tidusjar] + +- Fixed #998. [tidusjar] + +- Additional movie information. [Jamie.Rees] + +- Debug info around the notifications. [Jamie.Rees] + +- Small changes. [tidusjar] + +- Fixed #995. [tidusjar] + +- Fix for #978. [tidusjar] + +- Fixed #991. [tidusjar] + +- Fixed the login issue and pass Radarr the year #990. [Jamie.Rees] + +- More small tweaks around the username/alias. [Jamie.Rees] + +- Possible issue with the empty username. [Jamie.Rees] + +- Potential Fix for #985. [Jamie.Rees] + +- Small changed to the sidebar. [Jamie.Rees] + +- Small changes. [Jamie.Rees] + +- Done #627. [Jamie.Rees] + +- Finished #535 #445 #170. [tidusjar] + +- Fixed tests. [Jamie.Rees] + +- Started to add the specify Sonarr root folders. [Jamie.Rees] + +- Fixed #968. [Jamie.Rees] + +- Fixed #970. [Jamie.Rees] + +- Done #924. [Jamie.Rees] + +- Fixed. [Jamie.Rees] + +- Fixed #955. [Jamie.Rees] + +- #956. [Jamie.Rees] + +- Fixed #947. [Jamie.Rees] + +- #951. [tidusjar] + +- Finished #923 !!! [tidusjar] + +- More for #923. [Jamie.Rees] + +- Radarr integartion in progress #923. [tidusjar] + +- Fixed #940 don't show any shows without a tvdb id. [tidusjar] + +- Finished #739. [Jamie.Rees] + +- Initial impliementation of #739. [Jamie.Rees] + +- Improved the search UI and made it more consistant. Finished the Netflix API Part #884. [Jamie.Rees] + + +## v2.0.1 (2017-01-16) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added a netflix api. [Jamie.Rees] + +### **Fixes** + +- Fixed #934. [Jamie.Rees] + + +## v2.0 (2017-01-14) + +### **New Features** + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Added the settings for #925 but need to apply the settings to the UI. [Jamie.Rees] + +- Changed the settings name from Plex Requests to Ombi. [Jamie.Rees] + +- Added support for Managed Users #811. [Jamie.Rees] + +- Change solution name in travis. [mhann] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +### **Fixes** + +- Finished #925. [Jamie.Rees] + +- Some TODO's. [Jamie.Rees] + +- Fixed #915. [Jamie.Rees] + +- Fixed the issue where notifications are not being sent to users with Aliases #912. [Jamie.Rees] + +- Fixed #891. [Jamie.Rees] + +- Fix indentation issue. [Marcus Hann] + +- Implement simple button. [Marcus Hann] + +- Plex Username Case Sensitivity Fix. [thegame3202] + +- Fixed #882. [Jamie.Rees] + +- Api changed again, so more fixes for #878. [Jamie.Rees] + +- Possible fix for #893. [Jamie.Rees] + +- Fixed #898. [Jamie.Rees] + +- Fixed #878. [TidusJar] + +- * userManagementController.js: fixed #881. [TidusJar] + +- More work on watcher, should all be good now. #878. [Jamie.Rees] + +- Delete PlexRequests.sln.DotSettings. [Jamie] + +- Fixed #862. [Jamie.Rees] + +- #399 and #398 finished. [Jamie.Rees] + +- More work on #399. [Jamie.Rees] + +- Finished #884. [Jamie.Rees] + +- More for #844. [Jamie.Rees] + +- Another #844. [Jamie.Rees] + +- Fixed a dependancy issue with #844. [Jamie.Rees] + +- Finished the main part of #844 just need testing. [Jamie.Rees] + +- Fixed #832. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Fix tiny readme typo. [mhann] + +- Fixed #850 also started #844 (Wrote the API interaction) [Jamie.Rees] + +- #801 #292 done. [Jamie.Rees] + +- Should fix #841 #835 #810. [Jamie.Rees] + +- Fix small typo in ticket overview page. [mhann] + +- More work on the combined login. [Jamie.Rees] + +- Fixed db issue. [Jamie.Rees] + +- Name changes. [Jamie.Rees] + +- All Sln changes. [tidusjar] + +- Moved API Sln dir. [tidusjar] + +- Fixed build. [tidusjar] + +- Moved namespaces. [tidusjar] + +- Renamed zip. [tidusjar] + +- Product name change. [tidusjar] + + +## v1.10.1 (2016-12-17) + +### **Fixes** + +- #788 fixed! [tidusjar] + +- Fixed #788 and #791. [tidusjar] + +- #399 #398. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Small refactorings. [Jamie.Rees] + +- #782. [Jamie.Rees] + +- #785. [Jamie.Rees] + + +## v1.10.0 (2016-12-15) + +### **New Features** + +- Update README.md. [Jamie] + +- Added optional launch args for the auto updater. [Jamie.Rees] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update _Navbar.cshtml. [Jamie] + +- Update README.md. [Jamie] + +- Update _Navbar.cshtml. [Jamie] + +- Update README.md. [Jamie] + +- Added a new permission to bypass the request limit. [Jamie.Rees] + +- Update Version1100.cs. [SuperPotatoMen] + +- Added logging around the Newsletter #717. [Jamie.Rees] + +- Added missing migration. [tidusjar] + +- Added loading spinner. [Jamie.Rees] + +- Update UI.resx. [SuperPotatoMen] + +- Update Version1100.cs. [Jamie] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +### **Fixes** + +- Fixed an issue where the HTML in the newsletter was incorrect. [Jamie.Rees] + +- Fixed #201. [Jamie.Rees] + +- Fixed #720 and added better error handling around the migrations. [Jamie.Rees] + +- Fixed #769. [Jamie.Rees] + +- Write out the actual file version. [Jamie.Rees] + +- Error checking around GA. [TidusJar] + +- Fixed #761. [tidusjar] + +- Fixed #749 Fixed an issue where we were adding the read only permission when creating the admin. [tidusjar] + +- Fixed #757. [tidusjar] + +- Fixes around the server admin #754. [Jamie.Rees] + +- Removed the trace option from the UI, it is only accessible when appending the url with "?developer" #753. [Jamie.Rees] + +- Fixed an issue where the admin could not be updated. [Jamie.Rees] + +- Fixed #745. [Jamie.Rees] + +- Fixed #748. [Jamie.Rees] + +- Workaround for #748. [SuperPotatoMen] + +- Another attempt to fix #717. [tidusjar] + +- Fixed #744. [Jamie.Rees] + +- Tidied up the warnings. [Jamie.Rees] + +- Small bit of analytics. [Jamie.Rees] + +- Removed the whitelist. [Jamie.Rees] + +- Should fix #696 Fixed an issue with the scheduled jobs where it could use a different trigger if the order between the schedules and the triggers were in different positions in the array... Stupid me, ordering both arrays by the name now. [tidusjar] + +- Some better null object handling #731. [Jamie.Rees] + +- Small tweaks. [Jamie.Rees] + +- Fixed #728. [Jamie.Rees] + +- Fixed #727. [Jamie.Rees] + +- Fixed admin redirect issue. [tidusjar] + +- Fixed #718. [tidusjar] + +- Lots of small fixes and tweaks. [Jamie.Rees] + +- Tidied up some of the angular code, split the UI into it's own directives for easier maintainability. [Jamie.Rees] + +- Small tweaks to the Request Page. [Jamie.Rees] + +- Reverted the PR that may have caused #619. [Jamie.Rees] + +- Some small tweaks around #218 Just added the link to the settings and some angular improvements. [Jamie.Rees] + +- Test. [Jamie.Rees] + +- Attempt at fixing #686. [Jamie.Rees] + +- Finished #707. [Jamie.Rees] + +- Fixed #704. [Jamie.Rees] + +- Fixed #705. [Jamie.Rees] + +- Fixed #706. [Jamie.Rees] + +- #547. [Jamie.Rees] + +- Default tabs #304. [Jamie.Rees] + +- Fixed #703. [Jamie.Rees] + +- #233. [Jamie.Rees] + +- Done #678. [Jamie.Rees] + +- Fixed #670. [Jamie.Rees] + +- #456 Update all the requests when we identify that the username changes. [Jamie.Rees] + +- More user management. [Jamie.Rees] + +- #218. [Jamie.Rees] + +- Fixed build. [tidusjar] + +- Implimented the features #218. [tidusjar] + +- Use the user alias everywhere if it is set #218. [tidusjar] + +- Implimented auto approve permissions #218. [tidusjar] + +- A. [tidusjar] + +- Fixed an IOC issue. [tidusjar] + +- Fixed the issue with user management, needed to implement our own authentication provider. [Jamie.Rees] + +- Small changes including #666. [Jamie.Rees] + +- Done #679. [tidusjar] + +- Reduce the retry time. [Jamie.Rees] + +- Remove all references to the claims. [Jamie.Rees] + +- Lots of fixed and stuff. [Jamie.Rees] + +- Fixed potential crash #683. [Jamie.Rees] + +- Fixed #681. [Jamie.Rees] + +- More user management. [Jamie.Rees] + +- Finishing off the user management page #218 #359 #195. [Jamie.Rees] + +- Finished #646 and fixed #664. [Jamie.Rees] + +- Started on #646. Fixed #657. [Jamie.Rees] + +- Fixed #665. [Jamie.Rees] + +- Migrate users. [TidusJar] + +- Fixed build. [Jamie.Rees] + +- Convert the for to foreach for better readability. Still need to rework this area. [Jamie.Rees] + +- Final Tweaks #483. [Jamie.Rees] + +- Finished #483. [Jamie.Rees] + +- Finished the queue #483. [Jamie.Rees] + +- Started on the queue for requests #483 TV Requests with missing information has been completed. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Finished the notification for the fault queue. [Jamie.Rees] + +- Finished #556. [Jamie.Rees] + +- Finished #633 (First part of the queuing) [Jamie.Rees] + +- Finished #659 #236 has been modified slightly. Needs testing on Different systems. [Jamie.Rees] + +- Almost finished #659. [Jamie.Rees] + +- Started on #483. [Jamie.Rees] + +- #544. [Jamie.Rees] + +- Fixed #656 and more work on #218. [Jamie.Rees] + +- Fixed some issues with the user management work. [TidusJar] + +- Fixed build issue. [TidusJar] + +- User perms. [Jamie.Rees] + + +## v1.9.7 (2016-11-02) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Potential fix for #629. [TidusJar] + +- Fixed an issue to stop blatting over the base url. [tidusjar] + +- Fixed #643. [TidusJar] + +- Fixed #622. [TidusJar] + + +## v1.9.6 (2016-10-28) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Fixed #586. [Jamie.Rees] + +- Fixed #622. [Jamie.Rees] + +- Fixed #621. [Jamie.Rees] + + +## v1.9.5 (2016-10-27) + +### **New Features** + +- Added our own custom migrations, a lot easier to migrate DB versions now. [tidusjar] + +### **Fixes** + +- Bump version. [Jamie.Rees] + +- Small bit of work on the user claims. [Jamie.Rees] + +- Fix #612 again. [Jamie.Rees] + +- User management styling. [Jamie.Rees] + +- Fixed #608 and some other small stuff. [tidusjar] + +- More user mapping. [tidusjar] + +- Fixed #615. [tidusjar] + +- Fixed #610. [tidusjar] + +- User management stuff. [Jamie.Rees] + +- User management work. [Jamie.Rees] + +- Revert the TVSender to use the old code. [Jamie.Rees] + +- Fixed the view issue. [tidusjar] + +- S582: admin improvements part 2. [Jim MacKenzie] + +- Fix #612. [Jamie.Rees] + +- User management, migration and newsletter. [Jamie.Rees] + +- #602 recently added improvements. [tidusjar] + +- Revert "Sorting out the current state of migrations" [Jamie.Rees] + +- Sorting out the current state of migrations. [Jamie.Rees] + +- Marked as obsolete. [Jim MacKenzie] + +- Migration setup. [Jim MacKenzie] + +- Removed extra line breaks. [Jim MacKenzie] + +- Moved Newsletter Settings to its own page. [Jim MacKenzie] + +- Reverted TMDB package. [Jamie.Rees] + +- Remove DB Option. [Jamie.Rees] + +- Upgrade the movie DB package and fixed #370 To fix this I had to make another API call... It slows down the search... [tidusjar] + +- Lots of small fixes including #475. [tidusjar] + +- A better fix for #587. [tidusjar] + +- Fixed #553. [tidusjar] + +- #601. [Jamie.Rees] + +- More rework to use the Plex DB. [Jamie.Rees] + +- More work around using the PlexDatabase. [Jamie.Rees] + +- Plex DB. [Jamie.Rees] + +- Allow to process even know we had an error #578. [Jamie.Rees] + +- Fix boostrapper-datetimepicker imports (#586) [David Torosyan] + +- Potential work around for #587. [tidusjar] + +- Fixed #589. [tidusjar] + + +## v1.9.4 (2016-10-10) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added Paypalme options, no UI yet (#568) [Jim MacKenize] + +- Update appveyor.yml. [Jamie] + +- Update README.md. [Jamie] + +### **Fixes** + +- Reverted. [tidusjar] + +- Make sure it's enabled before sending the recently added. [tidusjar] + +- Moved the HR inside the table for TV Shows. [Jamie.Rees] + +- FIXED!!!!! YES BITCH! #550. [tidusjar] + +- Moved the horizontal rules inside the table row. [tidusjar] + + +## v1.9.3 (2016-10-09) + +### **New Features** + +- Added properties to disable tv requests for specific episodes or seasons and wired up to admin settings. [Matt McHughes] + +- Added different sonarr search commands. [tidusjar] + +### **Fixes** + +- Fixed #515. [tidusjar] + +- Fixed #561 and a small bit of work on #569. [tidusjar] + +- #569. [tidusjar] + +- Fixed case typo. [Matt McHughes] + +- Finished wiring tv request settings to tv search. [Matt McHughes] + +- WIP hide tv request options based on admin settings. [Matt McHughes] + +- Set meta charset to be utf-8. [Madeleine Schönemann] + +- F#552: updated labels text. [Jim MacKenize] + +- F#552: Re-design lables. [Jim MacKenzie] + +- Last correction.. Now the translation is ready to be used. [Michael Reber] + +- Forgot to correct two incorrect translations. [Michael Reber] + +- Correction of the German translation. [Michael Reber] + +- Notification improvements. [tidusjar] + +- #515. [tidusjar] + + +## v1.9.2 (2016-09-18) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update CouchPotatoCacher.cs. [Jamie] + +- Added some error handing around the GetMovie area #517. [tidusjar] + +- Added a version endpoint in "/api/version" #529. [tidusjar] + +### **Fixes** + +- Trying to fix the auto CP. [tidusjar] + +- Increase the notice message text box #527. [tidusjar] + +- #536 this should fix notification settings when it is being unsubscribed when testing. [tidusjar] + +- Improved how the TV search looks and feels. [tidusjar] + +- Fix for reverse proxy when using the wizard. [Devin Buhl] + +- Fixed #532. [tidusjar] + +- This should fix some issues with the episode requests #514. [tidusjar] + +- Small changes around existing series. [tidusjar] + +- Fixed #514 and the unit tests. [tidusjar] + +- If there is a bad password when changing it, we now inform the user. [tidusjar] + +- When logging out as admin remove the username from the session. [tidusjar] + +- Sorted out some of the UI for #18. [tidusjar] + +- Finished #18. [tidusjar] + + +## v1.9.1 (2016-08-30) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added french to the navbar. [tidusjar] + +- Changed the way we use the setTimeout function. Should fix #403 #491 #492. [tidusjar] + +- Change the redirection to use a relative uri redirect #473. [tidusjar] + +### **Fixes** + +- Fixed tests. [tidusjar] + +- Fixed #491 and added more logging around the email messages under the Info level. [tidusjar] + +- Finished #415. [tidusjar] + +- Fixed an issue where there were some JS errors on the landing page settings and stopped us being redirected to the login sometimes as an admin. [tidusjar] + +- Fixed #480. [tidusjar] + +- User management. [tidusjar] + +- Fixed #505. [tidusjar] + +- Append the application version to the end of our JS/CSS files. [tidusjar] + +- Fixed issue #487. [tidusjar] + +- Remove the datetime picker css from the main assets block and only load it on the pages it needs. #493. [tidusjar] + +- Redirect to search if we are already logged in #488. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixed an issue where you could set the base url as requests #479. [tidusjar] + +- Working on the beta releases page and also the user management. [tidusjar] + +- User work. [tidusjar] + + +## v1.9.0 (2016-08-18) + +### **New Features** + +- Update the availability checker to search for TV Episodes. [tidusjar] + +- Changed the no TVMazeid message. [tidusjar] + +- Added an option to disable/enable the Plex episode cacher. [tidusjar] + +- Updated the episode cacher to have a minimum of 11 hours before it runs again. [tidusjar] + +- Added some useful analytical infomation around the wizard. [tidusjar] + +- Updated the German translations #402. [tidusjar] + +- Added some code to shrink the DB. reworked the search to speed it up. [tidusjar] + +- Change to use the GrandparentTitle rather than the thumbnail.... facepalm. [tidusjar] + +- Change the interval to hours! [tidusjar] + +- Added the transaction back into the DB. Do not run the episode cacher if it's been run in the last hour. [tidusjar] + +- Added logging. [tidusjar] + +- Changed the query slightly. [tidusjar] + +- Updated Newtonsoft.Json, Autofixture, Nlog and Dapper packages. [tidusjar] + +- Added the Sonarr check for episodes #254. [tidusjar] + +- Added unit tests. [tidusjar] + +- Added #436. [tidusjar] + +- Update build no. [tidusjar] + +- Updated translations for #402. [tidusjar] + +- Added a beta module. [tidusjar] + +- Added a custom debug root path provider, this means we do not have to recompile the views every time we make a view change. [tidusjar] + +- Update .gitignore. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added tests for the string hash. [tidusjar] + +- Added code to request the api key for CouchPotato. [tidusjar] + +- Added the file version to the layout. [tidusjar] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added automation tests. [tidusjar] + +- Update appveyor.yml. [Jamie] + +- Updated packages. [tidusjar] + +- Updated Polly. [tidusjar] + +- Updated Fr, IT and NL translations #402. [tidusjar] + +- Changed the way the donate button works for #414. [tidusjar] + +- Added MediatR. [tidusjar] + +### **Fixes** + +- User management stuff. [tidusjar] + +- Fixed! [tidusjar] + +- Small amount of work on the user management. [tidusjar] + +- Fixed #466. [tidusjar] + +- Fixes. [tidusjar] + +- Made the episode request better. [tidusjar] + +- Removed commented out tests. [tidusjar] + +- Fixed the bad test after the merge. [tidusjar] + +- Reworked #466. [tidusjar] + +- More unit tests around the login and also the core Plex Checker. [tidusjar] + +- Potentially fixed the issue where we were requesting everything that was also available now. [tidusjar] + +- This should fix #466. [tidusjar] + +- Attempt at fixing a potential bug found from #466. [tidusjar] + +- #464 fixed. [tidusjar] + +- Fixed the build. [tidusjar] + +- Small improvements to the wizard. [tidusjar] + +- Always set the wizard to be true when editing the Plex Requests settings (Since the flag is not in the UI, a bool defaults to false). [tidusjar] + +- Tiny bit of more info. [tidusjar] + +- Finished #459. [tidusjar] + +- #459 is almost done. [tidusjar] + +- Modified the episode modal so that we are now resetting the button after a request. [tidusjar] + +- Commented out the transaction for now to debug it. [tidusjar] + +- Since we are multithreading, we should use a threadsafe type to store the episodes to prevent any threading or race conditions. [tidusjar] + +- Wrapped the bulk insert inside a transaction. [tidusjar] + +- Made the episode check parallel. [tidusjar] + +- Log out the GUID causing the issue. [tidusjar] + +- Fixed another test. [tidusjar] + +- Fixed tests. [tidusjar] + +- Got mostly everything working for #254 Ready for testing. [tidusjar] + +- Fixed issue with saving to db. [tidusjar] + +- Need to work out why the cacher is not working and where the datatype mismatch is. [tidusjar] + +- Don't delete first. [tidusjar] + +- Fix the log path issue #451. [tidusjar] + +- Dump an item. [tidusjar] + +- Small change with the return value in the batch insert. [tidusjar] + +- #254 Removed the cache, we are now storing the plex information into the database. [tidusjar] + +- Small change in the episode saver. [tidusjar] + +- Some small tweaks to improve the memory alloc. [tidusjar] + +- Short circuit when Plex hasn't been setup. Added Miniprofiler. [tidusjar] + +- Consolidate newtonsoft.json packages. [tidusjar] + +- Some performance improvements around the new TV stuff. [tidusjar] + +- Reworked the cacher, fixed the memory leak. No more logging within tight loops. [tidusjar] + +- Another null check. [tidusjar] + +- Some more changes. [tidusjar] + +- Some error handling. [tidusjar] + +- Check if the sonarr ep is monitored. [tidusjar] + +- Some logging. [tidusjar] + +- Small changes, we will actually see the episode cacher on the scheduled jobs page now. [tidusjar] + +- Work on the UI to show what episodes have been requested #254. [tidusjar] + +- Small fix. [tidusjar] + +- Fix the api change in #450. [tidusjar] + +- #254. [tidusjar] + +- Workaround for #440. [tidusjar] + +- Async async async improvements. [tidusjar] + +- Finished #266 Added a new cacher job to cache all episodes in Plex. [tidusjar] + +- Fixed #442. [tidusjar] + +- #254. [tidusjar] + +- Work around the sonarr bug #254. [tidusjar] + +- #254 having an issue with Sonarr. [tidusjar] + +- Small bit of work on #266. [tidusjar] + +- #254. [tidusjar] + +- Precheck and disable the episode boxes if we already have requested it. TODO check sonarr to see if it's already there. #254. [tidusjar] + +- Fixed broken build. [tidusjar] + +- More work for #254. [tidusjar] + +- More work on #254. [tidusjar] + +- Fixed the bug in #438 and added unit tests to make so we dont break it in the future. [tidusjar] + +- Some reason we had dupe translations. [tidusjar] + +- Rename SubDir to Base Url. [tidusjar] + +- Fix the exception in #440. [tidusjar] + +- Reworking the login page for #426. [tidusjar] + +- Fixed #438. [tidusjar] + +- Finished the auth stuff. [tidusjar] + +- Finished up the SMTP side of #429. [tidusjar] + +- #428 Added a message when the we cannot get a TVMaze ID. [tidusjar] + +- #254 MOSTLY DONE! At last, this took a while. [tidusjar] + +- Removed the other rootpath provider. [TidusJar] + +- Removed the other rootpath provider. [TidusJar] + +- Removed the other rootpath provider. [TidusJar] + +- Should fix #429. [TidusJar] + +- Done #135 We are including the application version number in the directory. [tidusjar] + +- #387 trim the spaces from the api key. Tidied up the setting models a bit. [tidusjar] + +- Wrapped the repo to catch Sqlite corrupt messages. [tidusjar] + +- Frontend and tv episodes api work for #254. [tidusjar] + +- #424. [tidusjar] + +- #359. [tidusjar] + +- Moved the plex auth token to the plex settings where it should belong. [tidusjar] + +- Small changes around the user management. [tidusjar] + +- Missed brace. [tidusjar] + +- Removed. [tidusjar] + +- Fixed issues from the merge. [tidusjar] + +- Stupid &$(*£ merge. [tidusjar] + +- Angular. [tidusjar] + +- Reworked the custom notifications... again. Need to figure out how to find the view to the model. [tidusjar] + +- Fixed #417. [tidusjar] + +- Removed NinjectConventions, we hadn't started to use it anyway. [tidusjar] + +- Fixed the way we will be using custom messages. [tidusjar] + +- Test checkin. [tidusjar] + +- Better handling for #388. [tidusjar] + +- Fixed #412. [tidusjar] + +- Fixed #413. [tidusjar] + +- Fixed #409. [tidusjar] + +- Trycatch around the availbility checker. [tidusjar] + +- WIP on notification resolver. [tidusjar] + +- Tidy. [tidusjar] + +- Plugged in MediatR. [tidusjar] + +- Moved over to using Ninject. [tidusjar] + + +## v1.8.4 (2016-06-30) + +### **Fixes** + +- Fixed the bug where we were auto approving everything. Added French language into the navigation bar. [tidusjar] + + +## v1.8.3 (2016-06-29) + +### **New Features** + +- Update README.md. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added some of the backend bits for #182. [tidusjar] + +- Updates for #243. [tidusjar] + +- Added Dutch language #243. [tidusjar] + +- Added languages #243. [tidusjar] + +- Added logging #350. [tidusjar] + +### **Fixes** + +- Small changes. [tidusjar] + +- Allow html in the notice message. [tidusjar] + +- Some more unit tests around the NotificationMessageResolver. [tidusjar] + +- Fixed a timing bug found the in build. Note, when working with time differences use TotalDays. [tidusjar] + +- More translations on the search page (Mainly the notification messages) #243. [tidusjar] + +- Fixed some warnings. [tidusjar] + +- CodeCleanup. [tidusjar] + +- Fixed a bit of a stupid bug in the resetter and added unit tests around it to make sure this never happens again. [tidusjar] + +- Fixed an issue where we didn't provide the correct response when clearing the logs. [tidusjar] + +- Made it so users that are in the whitelist do not have a request limit. [tidusjar] + +- Made it so the request limit doesn't apply to admin users. [tidusjar] + +- Fixed where a user could see the delete button on the issues page. [tidusjar] + +- Fixed some small issues and improved the navbar. [tidusjar] + +- Translated the Requested page #243. [tidusjar] + +- Finished #337. [tidusjar] + +- Some analytics. [tidusjar] + +- More translations for #243 and welcome text for #293. [tidusjar] + +- Small bit of work for #359. [tidusjar] + +- Finished #6. [tidusjar] + +- Analytics and fixes. [tidusjar] + +- Translated the search page #243. [tidusjar] + +- Implemented the different languages and added the ability to change cultures. #243. [tidusjar] + +- Started #243. [tidusjar] + +- Fixed #364. [tidusjar] + +- Some more useful analytical information. [tidusjar] + +- Generic try catch to fix #350. [tidusjar] + +- Slight changes, moved the donate button. [tidusjar] + +- Potential fix for #350. [tidusjar] + +- Better way of obtaining clean enum string. [Drewster727] + +- Fixed #362. [tidusjar] + + +## v1.8.2 (2016-06-22) + +### **New Features** + +- Update readme. [tidusjar] + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Fixed a circular reference issue. [tidusjar] + +- Small changes around how we work with custom events in the analytics. [tidusjar] + +- Fixed #353 #354 #355. [tidusjar] + +- Null provider check for movies. [Drewster727] + +- Show request type in notifications #346 and fix an issue from previous commit for #345. [Drewster727] + +- Add an option to stop sending notifications for requests that don't require approval #345. [Drewster727] + + +## v1.8.1 (2016-06-21) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Fix obj ref error when scheduler runs (ProviderId is null?) [Drewster727] + +- Fix logic for obtaining a sonarr quality profile #340. [Drewster727] + + +## v1.8.0 (2016-06-21) + +### **New Features** + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added the new advanced search into the search page too. [tidusjar] + +- Change the way we configure the IoC container in the bootstrapper, we are registering all the concrete instances on application start rather than on each web request. This should increase the performance per HTTP request. [tidusjar] + +- Updated nlog and fixed #295. [tidusjar] + +### **Fixes** + +- Workaround for #334. [Drewster727] + +- Create .gitattributes. [Jamie] + +- Fixes to the issues. [tidusjar] + +- Set the defaults for the landing page. [tidusjar] + +- Revert branch to 664dae2. [tidusjar] + +- Some unit tests for the issues. [tidusjar] + +- Tidied up the bootstrapper. [tidusjar] + +- Fix up landing page UI. [Drewster727] + +- Fixed CSS issue with the top arrow in the Plex theme. [tidusjar] + +- Small changes. [tidusjar] + +- Done #318. [tidusjar] + +- Fixed tests. [tidusjar] + +- #298 added some tests for the landing page. [tidusjar] + +- We are now only keeping the latest 1000 log records in the database. Delete everything else. [tidusjar] + +- Some analytic stuff. [tidusjar] + +- Capture the TVDBID when requesting. [tidusjar] + +- Attempting to improve #219. [tidusjar] + +- Just some more async changes. [tidusjar] + +- Small changes. [tidusjar] + +- More work on #298. Everything wired up. [tidusjar] + +- Fixed the issue on the landing page #298. [tidusjar] + +- #298 moved the content to the left a bit. [tidusjar] + +- Styling for #298 done, just need to wire up the model and do the actual status check. [tidusjar] + +- Bumped up the version number. [tidusjar] + +- Removed some DumpJson() from the trace logs. [tidusjar] + +- Small ui fix (100% width user/password fields to improve mobile experience) [Drewster727] + +- Landing page stuff #298. [tidusjar] + +- Datepicker UI fixes + small landing page UI fix. [Drewster727] + +- Removed a change that shoudn't have been commited. [tidusjar] + +- Fixed tests. [tidusjar] + +- More work for #298. [tidusjar] + +- #273 added for only available content on the search. [tidusjar] + +- Fixed #303 Looks like there was some incorrect business logic. [tidusjar] + +- Most of #273 done. [tidusjar] + +- Settings done for #298. [tidusjar] + +- Started #298. [tidusjar] + +- A crap tonne of work on #273. [tidusjar] + +- More work on #273. [tidusjar] + +- Reduced kept logs for 2 days. [tidusjar] + +- Fixed #300. [tidusjar] + +- #273. [tidusjar] + +- Fixed a bug with some users with the CP profiles. [tidusjar] + +- #273. [tidusjar] + +- Done the same for TV. [tidusjar] + +- Fixes #296. [tidusjar] + +- More for #273. [tidusjar] + +- Small changes. [tidusjar] + +- Revert "Small changes" [tidusjar] + +- Small changes. [tidusjar] + +- Finished #221 and added more async #278. [tidusjar] + +- Spelling mistake in the html! this fixes #264. [tidusjar] + +- More work on #273. [tidusjar] + +- Fixed #210. [tidusjar] + +- Started #273. [tidusjar] + + +## v1.7.5 (2016-05-29) + +### **New Features** + +- Update preview. [Jamie] + +- Updated dapper.contrib. Looks like there was a bug in the async methods. [tidusjar] + +- Updater wouldn't work when running a reverse proxy #236. [tidusjar] + +### **Fixes** + +- Bump build ver. [tidusjar] + +- Use HTTPS for the poster images, so there aren't any mixed content warnings when serving the application via an HTTPS reverse proxy. [Sean Callinan] + +- Removed static declarations. [tidusjar] + +- Fixed styling on modal. [tidusjar] + +- Made the search page all async goodness #278. [tidusjar] + +- Made the request module async #278. [tidusjar] + +- Started some dynamic scrolling. [tidusjar] + +- Stop dumping out the settings to the log. [tidusjar] + +- Made more async goodness. [tidusjar] + +- Made some of the searching async #278. [tidusjar] + +- Fixed #277. [tidusjar] + +- Reworked some tests. [tidusjar] + +- #26q make the auth users list taller. [Drewster727] + +- Fix 404 error. [Drewster727] + +- #262 make the auth users list taller. [Drewster727] + +- #221 delete requests per category. [Drewster727] + +- #256 #237 UI Improvements and consolidation. [Drewster727] + +- Fixed a bug in the user notification where if an admin wants to be notified they wouldn't be. [tidusjar] + +- Set the admin to have all claims. [tidusjar] + +- Fix null exception possibility in cp/sickrage cacher classes. [Drewster727] + +- Fixed #244. [tidusjar] + +- Fixed #240. [tidusjar] + +- Fixed #270. [tidusjar] + +- Fixed an issue where if you have only 1 plex friend it would not show in the list. [tidusjar] + + +## v1.7.4 (2016-05-25) + +### **New Features** + +- Update README.md. [Jamie] + +### **Fixes** + +- Fixed #252. [tidusjar] + +- Fixed #428. [tidusjar] + +- Version bump. [tidusjar] + +- Fixed tests. [tidusjar] + +- Fully fixed #239. [tidusjar] + +- We wan't updating the DB schema. [tidusjar] + + +## v1.7.3 (2016-05-25) + +### **Fixes** + +- Fixed the release build issue where we could not access the settings #239. [tidusjar] + + +## v1.7.2 (2016-05-25) + +### **Fixes** + +- Fixed a small bug where an exception would get thrown. [tidusjar] + +- Build version bump. [tidusjar] + +- Cleanup. [tidusjar] + +- Typo. [tidusjar] + +- Fixed #241. [tidusjar] + +- Fixed #239. [tidusjar] + +- Fixed #238. [tidusjar] + +- Small UI tweaks/improvements. [Drewster727] + + +## v1.7.1 (2016-05-24) + +### **New Features** + +- Update version. [tidusjar] + +### **Fixes** + +- Fixed an issue with the auth page when running with a reverse proxy. [tidusjar] + + +## v1.7 (2016-05-24) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added the ability to get the apikey from the api if you provide a correct username and password. Added more unit tests Added the ability to change a users password using the api refactored the Usermapper and made it unit testsable. [tidusjar] + +- Update. [tidusjar] + +- Added in an audit table. Since we are now allowing multiple users to change and modify things we need to audit this. [TidusJar] + +- Added the updater to the soloution and did a bit of starting code. [TidusJar] + +- Updated the claims so we can support more users. Added a user management section (not yet complete) Added the api to the solution and a api key in the settings (currently only gets the requests). [TidusJar] + +- Updated packages. [TidusJar] + +- Added a retry handler into the solution. We can now retry failed api requests. [TidusJar] + +- Update README.md. [Jamie] + +- Added Released propety to RequestViewModel. Added Released filter to the Requests page. [Chris Lees] + +- Added #27 to albums. [tidusjar] + +- Added the actual notification part of #27. [tidusjar] + +- Added the missing baseurl bit on the login page for #72. [tidusjar] + +- Added the 'enable user notifications' to the email settings view and model. [tidusjar] + +- Update README.md. [Jamie] + +### **Fixes** + +- Remove pointless test, change the default theme and fix a small bug. [tidusjar] + +- Fixed api. [tidusjar] + +- Finished #26. [tidusjar] + +- Plex theme. [tidusjar] + +- Implimented a theme changer, waiting for the Plex theme. [tidusjar] + +- Finished #222 #205. [tidusjar] + +- Started working on #26. [tidusjar] + +- Undid some small changes that was checked in by accident. [tidusjar] + +- #164 has been resolved. [tidusjar] + +- Resolved #224 , Removed the 'SSL' option from the email notification settings. We will now use the correct secure socket options (SSL/TLS) for your email host. [tidusjar] + +- Small changes. [tidusjar] + +- #27 fully finished. [tidusjar] + +- Fixed #215. [tidusjar] + +- Using Mailkit to fix #204. [tidusjar] + +- Color. [tidusjar] + +- Fully finished #27 just need to test it! [tidusjar] + +- Fixed test. [tidusjar] + +- Styling for #27. [tidusjar] + +- I think the auto updater is finished! #29. [tidusjar] + +- I think we have finished the main bulk of the auto updater #29. [tidusjar] + +- #222 #205 more ! Started getting the settings out. [tidusjar] + +- Removed the service locator from the base classes and added in some Api tests added all the tests back in! [tidusjar] + +- More work on the api and documentation #222 #205. [tidusjar] + +- Started documenting the API we now have swagger under ~/apidocs #222 #205. [tidusjar] + +- Api work for #205 Refactored how we check if the user has a valid api key Added POST request, PUT and DELTE. [tidusjar] + +- First pass of the updater working. #29. [tidusjar] + +- Removed SIGHUP from the termination list #220. [tidusjar] + +- Fixed. [tidusjar] + +- Missing. [tidusjar] + +- Missed out a file. [TidusJar] + +- And some more... [TidusJar] + +- Missed some files. [TidusJar] + +- A bit more work on switching to using user claims so we can support multiple users. [TidusJar] + +- Made the store backup clean up some of the older backups (> 7 days). [TidusJar] + +- More work on the user management. [TidusJar] + +- - Notifications will no longer be send to the admins if they request something. - Looks like we missed out adding the notifications to Music requests, so I added that in. [TidusJar] + +- - Improved the RetryHandler. - Made the tester buttons on the settings pages a bit more robust and added an indication when it's testing (spinner) [TidusJar] + +- Packages. [TidusJar] + +- Nm, [TidusJar] + +- Downgraded packages. [TidusJar] + +- Better handling for #202. [TidusJar] + +- Finished #208 and #202. [TidusJar] + +- This should help #202. [TidusJar] + +- Resolved #209. [TidusJar] + +- Finished #209. [TidusJar] + +- Slight adjustments to #189. [tidusjar] + +- - Added a visual indication on the UI to tell the admin there is a update available. - We are now also recording the last scheduled run in the database. [tidusjar] + +- Did the login bit on #185. [tidusjar] + +- Finished #186. [tidusjar] + +- Fixed #185. [tidusjar] + +- Fixed issue in #27 with albums. [tidusjar] + +- #27 added TV Search to the notification. [tidusjar] + +- Fixed bug. [tidusjar] + +- More work on #27 Added a new notify button to the search UI (Needs styling). Also fixed a bug where if the user could only see their own requests, if they search for something that has been requested, it will show as requested. [tidusjar] + +- Improved the startup of the application. We now properaly parse any args passed into the console. [tidusjar] + +- Additional cacher error handling + don't bother checking the requests when we don't get data back from plex. [Drewster727] + +- Remove old migration code and added new migration code. [tidusjar] + +- Stop the Cachers from bombing out when the response from the 3rd party api returns an exception or invalid response. #171. [tidusjar] + +- Increase the scheduler cache timeframe to avoid losing cache when the remote api endpoints go offline (due to a reboot or some other reason) -- if they're online, the cache will get refreshed every 10 minutes like normal. [Drewster727] + +- Fix the cacher by adding locking + extra logging in the plex checker + use a const key for scheduler caching time. [Drewster727] + +- Small changes. [tidusjar] + +- Switched out the schedulers, this seems to be a better implimentation to the previous and is easier to add new "jobs" in. [tidusjar] + +- Fixed #168. [tidusjar] + +- Fixed #162. [tidusjar] + +- Fix saving the log level. [Drewster727] + +- Set the max json length (fixes large json response errors) [Drewster727] + + +## v1.6.1 (2016-04-16) + +### **New Features** + +- Update README.md. [Jamie] + +- Added a url base. [tidusjar] + +- Change default logging. [tidusjar] + +- Added logging around SickRage. [tidusjar] + +### **Fixes** + +- Bump up the version number ready for the release. [tidusjar] + +- BaseUrl is finally finished! #72. [tidusjar] + +- #72 Login page done. [tidusjar] + +- More changes for the urlbase #72. [tidusjar] + +- Done the auth, cp, logs and sidebar for #72. [tidusjar] + +- Add an extra check when determining if a tv show is already available (also check if it starts with the show name returned from the tv db) [Drewster727] + +- Cache plex library data regardless of whether we have requests in the database or not. [Drewster727] + +- By default don't use a url base. [tidusjar] + +- Return empty array when obtaining queued IDs in sickrage cacher. [Drewster727] + +- Fixed a small bug in the SR cacher. [tidusjar] + +- Fixed when we do not have a base. [tidusjar] + +- More changes for #72. [tidusjar] + +- Fixed exception and all areas will now use the base url #72. [tidusjar] + +- Removed the test code from #72. [tidusjar] + +- Commented out the unit tests as they need to be reworked now. [tidusjar] + +- Finally fixed #72. [tidusjar] + +- Remove test code from plex api GetLibrary method. [Drewster727] + +- Finished up the caching TODO's. [tidusjar] + +- Kick off the schedulers once the web app has started (fixes api errors on start) [Drewster727] + +- Converted the UI back down to .NET 4.5.2. [tidusjar] + +- Fixed #154. [tidusjar] + +- Revert everything (except PlexRequests.UI) back to .NET 4.5.2 -- fixes incompatibilities with the latest version of mono (4.2.3.4) -- fixes notifications not working #152 #147 #141. [Drewster727] + +- #150 start caching plex media as well. refactored the availability checker. NEEDS TESTING. also, we need to make the Requests hit the plex api directly rather than hitting the cache as it does now. [Drewster727] + +- #150 split out the cache subscriptions to make sure they subscribe properly. [Drewster727] + +- #150 sonarr/sickrage cache checking. sickrage has a couple small items left. [Drewster727] + +- Fixed args. [tidusjar] + +- Fixed. [tidusjar] + +- Made the base better. [tidusjar] + +- Remove couchpotato api test code. [Drewster727] + +- Start the initial couchpotato cache call on a separate thread to keep the startup process quick. [Drewster727] + +- Add csproj with file changes from previous commit. [Drewster727] + +- Cache the couchpotato wanted list, update it on an interval, and use it to determine if a movie has been queued already. [Drewster727] + +- I think i've fixed an issue where SickRage reports Show not found. [tidusjar] + +- Set the default log level to info. #141. [tidusjar] + +- #125 refactor async task logic to work with mono. [Drewster727] + +- Fix search spinner sticking around after clearing search text + make the "Requested" and "Available" indicators in the search page different colors. [Drewster727] + +- #125 start indicating in the results if an item is already requested or available. [Drewster727] + +- #145 firefox css dsplay issue. [Drewster727] + +- Fixes for sonarr, we now display the error messages back to the user. [tidusjar] + +- Fixed #144. [tidusjar] + + +## v1.6.0 (2016-04-06) + +### **New Features** + +- Changed the build number. [tidusjar] + +- Update README.md. [Drew] + +- Update README.md. [Drew] + +- Update README.md. [Drew] + +- Update README.md. [Drew] + +- Changed the title to a contains but the artist still must match, [tidusjar] + +- Added unit tests to cover the new changes to the availability checker. [tidusjar] + +- Added the music check in the Plex Checker. [tidusjar] + +- Changed around the startup so we cache the profiles after the DB has been created. [tidusjar] + +- Updated where we update the request blobs schema change. [tidusjar] + +- Update SearchModule.cs. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Change the new columns type. [tidusjar] + +- Added a DBSchema so we have an easier way to update the DB. [tidusjar] + +- Added an issue template. [tidusjar] + +- Update README.md. [Jamie] + +- Added back the username into the Session when the admin logs in. This means they do not have to log in twice. [tidusjar] + +- Added happy path tests for the Checker. [tidusjar] + +- Added music to the search and requests page. [tidusjar] + +- Added a scroll to the top thingy and a bit more work on headphones. [tidusjar] + +- Added some tests and fixed the issue where the DB would get created in the wrong place depending on how you launched the application. [tidusjar] + +- Added the settings page for #32. [tidusjar] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Some final tweaks for #32. [tidusjar] + +- Fixed a bug where if we are the admin we didn't add the request to the db. [tidusjar] + +- Fixed an issue where we would add the Sickrage series but it would fail on adding the seasons. [tidusjar] + +- Properly account for future/past dates when humanizing with moment. [Drewster727] + +- Properly display release date on requests page. [Drewster727] + +- Add missing reference for release mode. [Drewster727] + +- #139 remove dependency and usage of humanize() - should help with cross-platform issues. start using moment.js. [Drewster727] + +- Fix selectors for music list on request page to get sorting working. [Drewster727] + +- Fixed the error #32. [tidusjar] + +- Fixed the logs page. [tidusjar] + +- Another attempt at filtering #32. [tidusjar] + +- A bit more error handling #32. [tidusjar] + +- Improved the availabilty check to include music results #32. [tidusjar] + +- Small changes for #32. [tidusjar] + +- A bit more logging for #32. [tidusjar] + +- More headphones #32 I am starting to hate headphones... Sometimes the artists and albums just randomly fail. [tidusjar] + +- #134 temporary workaround for this. [Drewster727] + +- Task.run for startup caching + fix admin module unit test failures. [Drewster727] + +- Cache injection, error handling and logging on startup, etc. [Drewster727] + +- Tweaks for #32. [tidusjar] + +- #132 auto-approve for admins. [Drewster727] + +- Finished the bulk work for Headphones. Needs testing #32. [tidusjar] + +- Made the album search 10x faster. We are now loading the images in a seperate call. #32. [tidusjar] + +- Add a reference to API Interfaces to fix the build. [tidusjar] + +- #114 start caching quality profiles. Set the cache on startup and when obtaining quality profiles in settings. [Drewster727] + +- Work for #32. [tidusjar] + +- #114 first pass at choosing quality profile when approving + focus search input by default and when switching tabs. [Drewster727] + +- #131 fix for default selected tab. [Drewster727] + +- Remove references to obsolete RequestedBy property + start setting the db schema to the app version, and check that in the future for migrations. [Drewster727] + +- Fixed async issue. [Shannon Barrett] + +- Updating SickRage api to verify Season List is up to date. [Shannon Barrett] + +- Work on showing the requests for #32. [tidusjar] + +- Got the search finished up for #32. [tidusjar] + +- Remove test/temp code in UserLoginModule. [Drewster727] + +- A bit more work on #32 started working on requesting it. The DB is a bit of an issue... [tidusjar] + +- Most of the UI work done for #32. [tidusjar] + +- Basic search working for #32. [tidusjar] + +- Mono datetime offset workaround. [Drewster727] + +- #122 store utc time in the databse + obtain timezone offset of the client upon login + offset times returned to client based on session offset. [Drewster727] + +- Method reference bug fix. [Drewster727] + +- Fix search focus z-index issue (hid suggestions options) [Drewster727] + +- Minor search UI adjustments. [Drewster727] + +- #55 first attempt at "suggestions" starting with "Comming Soon" and "In Theaters" [Drewster727] + +- #106 rename sorting options and polish the dropdown UI a bit. [Drewster727] + +- Started adding the api part for headphones #32. [tidusjar] + +- Upped the time of #123. [tidusjar] + +- First attempt at #123. [tidusjar] + +- We now do not show the text Requested By to the user, we also show a 'success' message instead of a warning when something has already been requested. [tidusjar] + +- Show a "no requests yet" message on the requests page (for each cateogory) [Drewster727] + +- Ignore items that are already available when approving in bulk, and simplify the checking + compile css. [Drewster727] + +- Add a better way to merge RequestedBy and RequestedUsers to avoid code duplication and simplify checks. [Drewster727] + +- Don't query the session as much in the modules, rely on a variable from the base class and store the username as needed. [Drewster727] + +- Show the requested by user from legacy request models. [Drewster727] + +- Only show requested by users to admins + start maintaining a list of users with each request. [Drewster727] + +- #96 fix up notification test feature. [Drewster727] + +- Fix the request page sort/approve button alignment. [Drewster727] + +- When pulling requests, set each to approved that is already available (so the UI avoids showing the approve option for already available content) [Drewster727] + +- Mono doesn't seem to have Tls1.2. Let's try TLS 1 #119. [tidusjar] + +- Specify a protocol type of TLS12. Looks like CP doesn't seem to like SSL3 (it is quite old now so understandable) #119. [tidusjar] + +- Made #85 better. [tidusjar] + +- Fixed the tests. [tidusjar] + +- Made the feedback from Sonarr better when Sonarr already has the series #85. [tidusjar] + +- An attempt to fix #108. [tidusjar] + +- Add some "no results" feedback to the searching + minor UI improvements. [Drewster727] + +- Fix notification tests. [Drewster727] + +- UI - increase icon size of nav menu (they were too small before) [Drewster727] + +- #96 Finished adding test functionality to notifications. [Drewster727] + +- #96 add the necessary back-end code to produce a test message for all notification types (still have to add the test buttons for pushbullet/pushover) [Drewster727] + +- #96 modify notifications interface/service to accept a non-type specific settings object. [Drewster727] + +- #96 Email notification test button (others to come) [Drewster727] + +- Minor UI adjustments. [Drewster727] + +- #84 provide an option in settings to resttrict users from viewing requests other than their own. [Drewster727] + +- #54 comma separated list of users who don't require approval + fix a couple request messages (include show title) [Drewster727] + +- Clean up the sorting option names. add a way to see which filter/sort is currently applied. [Drewster727] + +- Fix up the animations. seems to be related to the data-bound attribute causing the animtions not to fire on each .mix object. [Drewster727] + +- Move approve buttons to the tab content. [Drewster727] + +- Allow approving all requests by category. [Drewster727] + +- Fix up sorting on the request page. [Drewster727] + +- Add ubuntu/debian instructions. [Drewster727] + +- #86 - display movie/show title + year in request notifications. [Drewster727] + +- Show the movie/show title when requesting. [Drewster727] + + +## v1.5.2 (2016-03-26) + +### **Fixes** + +- Stoped users from spamming the request button. [tidusjar] + +- Fixed the logger no longer writing to the file. [tidusjar] + +- Fixed #97. [tidusjar] + + +## v1.5.1 (2016-03-26) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added logs to the sidebar. I'm an idiot. [tidusjar] + +### **Fixes** + +- Approve tv shows or movies. [Drewster727] + +- Fixed a bug where if you had auto approve it wouldn't notify you. [tidusjar] + + +## v1.5.0 (2016-03-25) + +### **New Features** + +- Updated version number for release. [tidusjar] + +- Updated the logic for handling specific seasons in Sonarr and Sickrage. [Shannon Barrett] + +- Updated the readme and added some icons to the navbar. [tidusjar] + +- Added the ability to sepcify a username in the email notification settings for external MTA's. We have had to add a new option called Email Sender because of this. #78. [tidusjar] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added a notification model to the notifiers. Added the backend work for sending a notification for an issue report #75. [tidusjar] + +- Added a subdir to CP, SickRage, Sonarr and Plex #43. [tidusjar] + +### **Fixes** + +- And again. [tidusjar] + +- Made the check actually work. [tidusjar] + +- Finished up #68 and #62. [tidusjar] + +- Finished styling on the logger for now. #59. [tidusjar] + +- Fixed #69. [tidusjar] + +- Working on getting the Sonarr component to work correctly. [Shannon Barrett] + +- Fixes issue #62. [Shannon Barrett] + +- Refactored the Notification service to how it should have really been done in the first place. [tidusjar] + +- Fixed the build. [tidusjar] + +- Finished #49. [tidusjar] + +- Finished #57. [tidusjar] + +- Small changes around the filtering. [tidusjar] + +- Finished adding pushover support. #44. [tidusjar] + +- Resolved #75. [tidusjar] + +- Include DB changes. [tidusjar] + +- Done most on #59. [tidusjar] + +- Lowercase logs folder, because you know, linux. #59. [tidusjar] + +- Adding the imdb when requesting. [tidusjar] + +- Fixed an issue where the table didn't match the model. [tidusjar] + +- Improved the status page with the suggestion from #29. [tidusjar] + +- Hooked up most of #49 Just the validation messages need to be done. [tidusjar] + +- Fixed #74 and #64. [tidusjar] + +- Resolved #70. [tidusjar] + +- Finished #71. [tidusjar] + +- Got the filter working on both movie and tv #57. [tidusjar] + +- Started #57, currently there is a bug where the TV list won't filter. [tidusjar] + + +## v1.4.1 (2016-03-20) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update AvailabilityUpdateService.cs. [Jamie] + + +## v1.4.0 (2016-03-19) + +### **New Features** + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Updated the build version ready for the next release. [tidusjar] + +- Added the api and settings page for Sickrage. Just need to do the tester and hook it up #40. [tidusjar] + +- Added the option to set a CP quality #38. [tidusjar] + +- Added the code to lookup the old requests and refresh them with new information from TVMaze. [tidusjar] + +- Update StatusCheckerTests.cs. [Jamie] + +- Update README.md. [Jamie] + +- Added TVMaze to the search. #21. [tidusjar] + +- Added migration code and cleaned up the DB. [tidusjar] + +- Updated the way we add requests. [tidusjar] + +- Updated the Dapper.Contrib package, it had a bug where it wasn't returning the correct Id from inserts. [tidusjar] + +### **Fixes** + +- This fixes #36. [tidusjar] + +- Should fix issue #36. [Shannon Barrett] + +- When we do a batch update we need to reset the cache. [tidusjar] + +- Fixed an issue where the default quality on Sickrage wouldn't work. [tidusjar] + +- Wow, that was a lot of work. - So, I have now finished #40. - Fixed a bug where we was not choosing the correct tv series (Because of TVMaze) - Fixed a bug when checking for plex titles - Fixed a bug where the wrong issue would clean on the UI (DB was correct) - Refactored how we send tv shows - And too many small changes to count. [tidusjar] + +- Fixed the new dependancy with the admin class tests. [tidusjar] + +- Back to what it was :( [tidusjar] + +- Another test for #37. [tidusjar] + +- This should fix #37. [Jamie Rees] + +- Catch the missing table exception when they have a new DB. [Jamie Rees] + +- Exploratory test for #37. [Jamie Rees] + +- Fixed #33 we now have SSL options for Sonarr and CP. [Jamie Rees] + +- Removed all the html from the new TVMaze api (for overview). Added tests to cover the html removal. updated Readme to remove TheTVDB. [Jamie Rees] + +- Fixed tests. [Jamie Rees] + +- Almost fully integrated TVMaze #21 and also improved the fix for #31. [Jamie Rees] + +- Should fix #28. [Shannon Barrett] + +- Fixed #16 and #30. [tidusjar] + +- Modified the adding of request to update the model with the added ID. [tidusjar] + +- Switched over to the new service. [tidusjar] + +- Fixed #25. [Jamie Rees] + + +## v1.3.0 (2016-03-17) + +### **New Features** + +- Added pushbullet to the sidebar. [Jamie Rees] + +- Updated build version for the next release. [Jamie Rees] + +- Updated readme link. [tidusjar] + +- Added ignore to static tests. [tidusjar] + +- Added Pushbullet notifications #8. [tidusjar] + +- Added first implimentation of the Notification Service #8 Added tests to cover the notification service. [tidusjar] + +- Added validation to the Email settings, also increased the availability checker from 2 minutes to 5. [tidusjar] + +### **Fixes** + +- Fixed #22. [Jamie Rees] + +- Started on #16, nothing is hooked up yet. [tidusjar] + +- Fixed tests. [tidusjar] + + +## v1.2.1 (2016-03-16) + +### **New Features** + +- Update Program.cs. [Jamie] + +- Update Program.cs. [Jamie] + +- Added back the reference. [tidusjar] + +### **Fixes** + +- Removed the email notification settings from the settings (for release 1.2.1) [Jamie Rees] + +- Fixed. [Jamie Rees] + +- Resolved #10. [tidusjar] + + +## v1.2.0 (2016-03-15) + +### **New Features** + +- Updated. [Jamie Rees] + +- Updated appveyor. [Jamie Rees] + +- Update appveyor.yml. [Jamie] + +- Added latest version code and view. Need to finish the view #11. [tidusjar] + +- Added test button to Plex. That's fixed #9. [tidusjar] + +- Added test sonarr button #9. [tidusjar] + +- Added more tests. [tidusjar] + +- Added a bunch of logging. [tidusjar] + +- Added the application tester for CP #9. [tidusjar] + +- Added settings page for #8. [tidusjar] + +- Added pace.js. [tidusjar] + +### **Fixes** + +- Finished the notes! Resolved #7. [Jamie Rees] + +- #12. [Jamie Rees] + +- #12. [Jamie Rees] + +- Finished the status page #11 and some more work to #12. [Jamie Rees] + +- Resolved #7. [tidusjar] + +- Small changes. [tidusjar] + +- Yeah... [tidusjar] + +- Fixed #5 and also added some tests to the availability checker. [tidusjar] + +- Started added tests. [Jamie Rees] + +- Fixed an issue where the issues text appears larger. [Jamie Rees] + + +## v1.1 (2016-03-13) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Updated readme. [Jamie Rees] + +- Added the support for TV Series integrating with Sonarr. [Jamie Rees] + +- Added the functionality to pass a port through an argument. [tidusjar] + +- Added the code to get the quality profiles from Sonarr Started plugging that into the UI. [Jamie Rees] + +- Added the spinners #3. [tidusjar] + +- Added the functionality for the admin to clear the issues. [tidusjar] + +- Added the issues to the requests page. [tidusjar] + +- Added user logout method and unit tests to cover it. [tidusjar] + +- Added DeniedUsers to the view. [tidusjar] + +- Added the denied user check to the UserLoginModule. added a test case to cover it. [tidusjar] + +- Added a missing reference. [tidusjar] + +- Added first real test. [tidusjar] + +- Update README.md. [Jamie] + +- Added the latest version of nuget. [tidusjar] + +- Added travisyml. [tidusjar] + +- Added logging. [tidusjar] + +- Added missing files. [tidusjar] + +- Update README.md. [Jamie] + +- Added logging (Still WIP) [tidusjar] + +- Added favicon and also structured the HTML correctly. [tidusjar] + +- Updated the packages so everything is now with the correct framework (4.5.2) [tidusjar] + +- Added in deletion of requests. [tidusjar] + +- Added test code. [tidusjar] + +- Added dashboard. [tidusjar] + +- Added couchpotato page. [Jamie Rees] + +- Added readme to the project and updated it. [Jamie Rees] + +- Added helpers. [tidusjar] + +### **Fixes** + +- Bug fix, Couchpotato settings wouldn't show in release due to a Nancy bug. [Jamie Rees] + +- Small changes. [Jamie Rees] + +- First release, build 1.0.0. [Jamie Rees] + +- Removed the request limit since it's not currently being used. [Jamie Rees] + +- REmoved Sickbeared for the first release. [Jamie Rees] + +- Fixed #4 We now can manually set the status of a request. [tidusjar] + +- Made the pass in the port a bit more robust. [tidusjar] + +- Styling, Added the functionality for the Sonarr Profiles on the Admin page #2 resolved. [tidusjar] + +- Fixed a bug in the Login and added a unit test to cover that. Added a button to approve an individual request. Fixed some minor bugs in the request screen. [Jamie Rees] + +- Fixed the 'responsive' issue for the search and requests pages #3. [tidusjar] + +- Styling! #3. [tidusjar] + +- Navbar category now will follow you to various screens #3. [tidusjar] + +- Fixed bugs with the 'other' reporting issue and also the clear issues. [tidusjar] + +- We now are appending the users name to who wrote the comment. Rather than it being unknown. [tidusjar] + +- More work on submitting issues. [tidusjar] + +- More test changes. [tidusjar] + +- More tests to cover the login. [tidusjar] + +- Refactoring. [tidusjar] + +- Implimented the password part and authentication with Plex. [tidusjar] + +- Initial Use authentication is working. Need to do the password bit. [tidusjar] + +- Some error handling and ensure we are an admin to delete requests. [tidusjar] + +- Fixed the issue where the Release build would not show the admin screens! [tidusjar] + +- Fixes. [tidusjar] + +- Removed the DI part of the service. TinyIOC doesn't want to work with FluentScheduler. [tidusjar] + +- First pass at the plex update service. [tidusjar] + +- Small changes. [Jamie Rees] + +- Started to impliment the Plex checker. This will check plex every x minutes to see if there is any new content and then update the avalibility of the requests. [Jamie Rees] + +- Mre work. [Jamie Rees] + +- Few small changes, added plex settings. [Jamie Rees] + +- Making the configuration actually do something. Setting a default configuration if there is no DB. [Jamie Rees] + +- Remove post build. [Jamie Rees] + +- Small changes. [Jamie Rees] + +- MOre work. [Jamie Rees] + +- Fixed the issue when sending movies to CouchPotato. [Jamie Rees] + +- Add appveyor. [tidusjar] + +- Build it on 4.5. [tidusjar] + +- Upgraded .net to 4.6. [tidusjar] + +- Typo2. [tidusjar] + +- Typo. [tidusjar] + +- Another update. [tidusjar] + +- Fixed. [tidusjar] + +- More logging to figure out why the we cannot access the admin module in a release build. [tidusjar] + +- Firstpass integrating with CouchPotato. [tidusjar] + +- Some styling. [tidusjar] + +- Fixed the plex friends. Added some unit tests, moved the plex auth into it's own page. [tidusjar] + +- Fully switched the TV shows over to use the other provider. [Jamie Rees] + +- Renamed folders. [tidusjar] + +- Assembly updates. [tidusjar] + +- Moved the rest of the projects. [tidusjar] + +- Moved UI. [tidusjar] + +- Mass rename. [tidusjar] + +- Quick changes. [tidusjar] + +- Started switching the TV over to the new provider (TheTVDB). Currently TV search is partially broken. It will search but we are not mapping all of the details. [tidusjar] + +- Implimented the new TV show Provider (needed for Sonarr TheTvDB) [tidusjar] + +- Started the user auth. [tidusjar] + +- Some work on the requests page. [tidusjar] + +- Made the 'requested' better and made the remove look nicer. [tidusjar] + +- Cleaned up the program a tiny bit. [tidusjar] + +- Removed additional namespace. [tidusjar] + +- Fixed some db issues and added a preview. [Jamie Rees] + +- More work on the settings. [Jamie Rees] + +- Upgraded Json.Net and Nancy packages. [Jamie Rees] + +- Plex friends api. [Jamie Rees] + +- Enabled trace logs. [tidusjar] + +- Sql syntax issue fixed. [tidusjar] + +- Fixed release build. [tidusjar] + +- Small updates including assembly version. [tidusjar] + +- Work on the requests page mostly done. [tidusjar] + +- Work on the TV request. the `latest` parameter is not being passed into the requestTvshow. [tidusjar] + +- Missing file. [tidusjar] + +- Using the IoC container now. [tidusjar] + +- Some plex work. [Jamie Rees] + +- More work. [Jamie Rees] + +- Removed the setup code out of the startup, since we attemtp to connect to the DB before that. [Jamie Rees] + +- Some more work. Need to stop the form submitting on a request. [tidusjar] + +- Moved everything up a directory. [tidusjar] + +- Lots of work! [tidusjar] + +- Done most of the movie search work. [Jamie Rees] + +- First pass with RequestPlex. [tidusjar] + +- Initial commit. [Jamie] + + From 827ea857e0c818af3a952b43add2278f3349fbe0 Mon Sep 17 00:00:00 2001 From: Anojh Date: Wed, 11 Apr 2018 13:55:36 -0700 Subject: [PATCH 018/495] Inject base url if set before theme file url, see issue #1795 --- src/Ombi/Controllers/SettingsController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 9ffa9d81f..44702a073 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -274,6 +274,12 @@ namespace Ombi.Controllers public async Task GetThemeContent([FromQuery]string url) { var css = await _githubApi.GetThemesRawContent(url); + var ombiSettings = await OmbiSettings(); + if (ombiSettings.BaseUrl != null) + { + int index = css.IndexOf("/api/"); + css = css.Insert(index, ombiSettings.BaseUrl); + } return Content(css, "text/css"); } From 7d62b4a712bd38829f7bde3275fcc11f5f584ade Mon Sep 17 00:00:00 2001 From: Jamie Date: Thu, 12 Apr 2018 09:14:32 +0100 Subject: [PATCH 019/495] Removed some early disposition that seemed to be causing errors in the API --- src/Ombi/StartupExtensions.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Ombi/StartupExtensions.cs b/src/Ombi/StartupExtensions.cs index 1c8f54b4e..e4dae18e4 100644 --- a/src/Ombi/StartupExtensions.cs +++ b/src/Ombi/StartupExtensions.cs @@ -168,7 +168,6 @@ namespace Ombi if (user == null) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - context.Response.RegisterForDispose(um); await context.Response.WriteAsync("Invalid User Access Token"); } else @@ -178,7 +177,6 @@ namespace Ombi var roles = await um.GetRolesAsync(user); var principal = new GenericPrincipal(identity, roles.ToArray()); context.User = principal; - context.Response.RegisterForDispose(um); await next(); } } @@ -191,7 +189,6 @@ namespace Ombi if (!valid) { context.Response.StatusCode = (int)HttpStatusCode.Unauthorized; - context.Response.RegisterForDispose(settingsProvider); await context.Response.WriteAsync("Invalid API Key"); } else @@ -199,7 +196,6 @@ namespace Ombi var identity = new GenericIdentity("API"); var principal = new GenericPrincipal(identity, new[] { "Admin", "ApiUser" }); context.User = principal; - context.Response.RegisterForDispose(settingsProvider); await next(); } } From 7a7b00ab25aaa234b62a29cf92dd6d98aa84dcb1 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Fri, 13 Apr 2018 14:19:21 +0100 Subject: [PATCH 020/495] Add base url as a startup argument #2153 --- .../Entities/ApplicationConfiguration.cs | 1 + src/Ombi/Program.cs | 25 +++++++++++++++++++ src/Ombi/Startup.cs | 14 +++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/Ombi.Store/Entities/ApplicationConfiguration.cs b/src/Ombi.Store/Entities/ApplicationConfiguration.cs index 1499ca34f..809264312 100644 --- a/src/Ombi.Store/Entities/ApplicationConfiguration.cs +++ b/src/Ombi.Store/Entities/ApplicationConfiguration.cs @@ -17,5 +17,6 @@ namespace Ombi.Store.Entities TheMovieDb = 4, StoragePath = 5, Notification = 6, + BaseUrl=7, } } \ No newline at end of file diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index 9ced1715e..9294852f9 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -23,11 +23,13 @@ namespace Ombi var host = string.Empty; var storagePath = string.Empty; + var baseUrl = string.Empty; var result = Parser.Default.ParseArguments(args) .WithParsed(o => { host = o.Host; storagePath = o.StoragePath; + baseUrl = o.BaseUrl; }).WithNotParsed(err => { foreach (var e in err) @@ -47,6 +49,7 @@ namespace Ombi { var config = ctx.ApplicationConfigurations.ToList(); var url = config.FirstOrDefault(x => x.Type == ConfigurationTypes.Url); + var dbBaseUrl = config.FirstOrDefault(x => x.Type == ConfigurationTypes.BaseUrl); if (url == null) { url = new ApplicationConfiguration @@ -65,6 +68,25 @@ namespace Ombi ctx.SaveChanges(); urlValue = url.Value; } + + if (dbBaseUrl == null) + { + if (baseUrl.HasValue() && baseUrl.StartsWith("/")) + { + dbBaseUrl = new ApplicationConfiguration + { + Type = ConfigurationTypes.BaseUrl, + Value = baseUrl + }; + ctx.ApplicationConfigurations.Add(dbBaseUrl); + ctx.SaveChanges(); + } + } + else if(!baseUrl.Equals(dbBaseUrl.Value)) + { + dbBaseUrl.Value = baseUrl; + ctx.SaveChanges(); + } } DeleteSchedulesDb(); @@ -118,5 +140,8 @@ namespace Ombi [Option("storage", Required = false, HelpText = "Storage path, where we save the logs and database")] public string StoragePath { get; set; } + [Option("baseurl", Required = false, HelpText = "The base URL for reverse proxy scenarios")] + public string BaseUrl { get; set; } + } } diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 4aaadacb6..7a94dbb61 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -32,6 +32,7 @@ using Ombi.Schedule; using Ombi.Settings.Settings.Models; using Ombi.Store.Context; using Ombi.Store.Entities; +using Ombi.Store.Repository; using Serilog; using Serilog.Events; @@ -176,6 +177,19 @@ namespace Ombi { app.UsePathBase(settings.BaseUrl); } + else + { + // Check if it's in the startup args + var appConfig = serviceProvider.GetService(); + var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl).Result; + if (baseUrl.Value.HasValue()) + { + settings.BaseUrl = baseUrl.Value; + ombiService.SaveSettings(settings); + + app.UsePathBase(settings.BaseUrl); + } + } app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 1, ServerTimeout = TimeSpan.FromDays(1), ShutdownTimeout = TimeSpan.FromDays(1)}); app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire", From 1d4d3c547600070fd7318e6678d663bacee532f5 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Fri, 13 Apr 2018 14:21:23 +0100 Subject: [PATCH 021/495] !wip added some missing code --- src/Ombi.Helpers/OmbiRoles.cs | 2 ++ src/Ombi/Controllers/IdentityController.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/src/Ombi.Helpers/OmbiRoles.cs b/src/Ombi.Helpers/OmbiRoles.cs index e7527279d..ba8c8d087 100644 --- a/src/Ombi.Helpers/OmbiRoles.cs +++ b/src/Ombi.Helpers/OmbiRoles.cs @@ -2,6 +2,8 @@ { public static class OmbiRoles { + // DONT FORGET TO ADD TO IDENTITYCONTROLLER.CREATEROLES AND THE UI! + public const string Admin = nameof(Admin); public const string AutoApproveMovie = nameof(AutoApproveMovie); public const string AutoApproveTv = nameof(AutoApproveTv); diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index 6a8aee52c..5db5f2168 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -194,6 +194,7 @@ namespace Ombi.Controllers await CreateRole(OmbiRoles.RequestMovie); await CreateRole(OmbiRoles.RequestTv); await CreateRole(OmbiRoles.Disabled); + await CreateRole(OmbiRoles.RecievesNewsletter); } private async Task CreateRole(string role) From a681932d2e5d33e7d3d3a25f179027ae69421466 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 13 Apr 2018 22:38:38 +0100 Subject: [PATCH 022/495] Fixed a bug with the RefreshMetadata where we would never get TheMovieDBId's if it was missing it --- .../Jobs/Ombi/RefreshMetadata.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs index 9d073facf..9b7726a15 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/RefreshMetadata.cs @@ -80,7 +80,7 @@ namespace Ombi.Schedule.Jobs.Ombi if (!hasTheMovieDb) { - var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title); + var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title, false); show.TheMovieDbId = id; } @@ -120,7 +120,7 @@ namespace Ombi.Schedule.Jobs.Ombi if (!hasTheMovieDb) { - var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title); + var id = await GetTheMovieDbId(hasTvDbId, hasImdb, show.TvDbId, show.ImdbId, show.Title, false); show.TheMovieDbId = id; } @@ -166,7 +166,7 @@ namespace Ombi.Schedule.Jobs.Ombi } if (!hasTheMovieDb) { - var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title); + var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title, true); movie.TheMovieDbId = id; _plexRepo.UpdateWithoutSave(movie); } @@ -200,7 +200,7 @@ namespace Ombi.Schedule.Jobs.Ombi } if (!hasTheMovieDb) { - var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title); + var id = await GetTheMovieDbId(false, hasImdb, string.Empty, movie.ImdbId, movie.Title, true); movie.TheMovieDbId = id; _embyRepo.UpdateWithoutSave(movie); } @@ -215,7 +215,7 @@ namespace Ombi.Schedule.Jobs.Ombi await _embyRepo.SaveChangesAsync(); } - private async Task GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title) + private async Task GetTheMovieDbId(bool hasTvDbId, bool hasImdb, string tvdbID, string imdbId, string title, bool movie) { _log.LogInformation("The Media item {0} does not have a TheMovieDbId, searching for TheMovieDbId", title); FindResult result = null; @@ -230,13 +230,29 @@ namespace Ombi.Schedule.Jobs.Ombi if (hasImdb && !hasResult) { result = await _movieApi.Find(imdbId, ExternalSource.imdb_id); - hasResult = result?.tv_results?.Length > 0; + if (movie) + { + hasResult = result?.movie_results?.Length > 0; + } + else + { + hasResult = result?.tv_results?.Length > 0; + + } _log.LogInformation("Setting Show {0} because we have ImdbId, result: {1}", title, hasResult); } if (hasResult) { - return result.tv_results?[0]?.id.ToString() ?? string.Empty; + if (movie) + { + return result.movie_results?[0]?.id.ToString() ?? string.Empty; + } + else + { + + return result.tv_results?[0]?.id.ToString() ?? string.Empty; + } } return string.Empty; } From eeaf614a29d2a1c063b4b9250cb86352b9b5af92 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 13 Apr 2018 23:19:57 +0100 Subject: [PATCH 023/495] Added a new Job. Plex Recently Added, this is a slimmed down version of the Plex Sync job, this will just scan the recently added list and not the whole library. I'd reccomend running this very regulary and the full scan not as regular. --- CHANGELOG.md | 3622 ----------------- src/Ombi.Api.Plex/IPlexApi.cs | 1 + src/Ombi.Api.Plex/Models/Metadata.cs | 4 +- src/Ombi.Api.Plex/PlexApi.cs | 9 + src/Ombi.DependencyInjection/IocExtensions.cs | 1 + src/Ombi.Schedule/JobSetup.cs | 7 +- .../Jobs/Plex/Interfaces/IPlexContentSync.cs | 2 +- .../Plex/Interfaces/IPlexRecentlyAddedSync.cs | 9 + .../Jobs/Plex/PlexContentSync.cs | 34 +- .../Jobs/Plex/PlexRecentlyAddedSync.cs | 40 + .../Settings/Models/JobSettings.cs | 1 + .../Settings/Models/JobSettingsHelper.cs | 7 +- .../ClientApp/app/interfaces/ISettings.ts | 1 + .../ClientApp/app/services/job.service.ts | 4 + .../app/settings/jobs/jobs.component.html | 6 + .../app/settings/jobs/jobs.component.ts | 1 + .../app/settings/plex/plex.component.html | 19 +- .../app/settings/plex/plex.component.ts | 10 +- src/Ombi/Controllers/JobController.cs | 13 +- src/Ombi/Controllers/SettingsController.cs | 1 + src/Ombi/Startup.cs | 11 +- 21 files changed, 152 insertions(+), 3651 deletions(-) create mode 100644 src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs create mode 100644 src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bfe7e633..5c557ed19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,20 +8,10 @@ - Added the ability to turn off TV or Movies from the newsletter. [Jamie] -- Update about.component.html. [Jamie] - -- Update about.component.html. [Jamie] - - Added random versioning prefix to the translations so the users don't have to clear the cache. [Jamie] - Added more information to the about page. [Jamie] -- Changed let to const to adhere to linting. [Anojh] - -- Update _Layout.cshtml. [goldenpipes] - -- Update _Layout.cshtml. [goldenpipes] - - Changed the TV Request API. We now only require the TvDbId and the seasons and episodes that you want to request. This should make integration regarding TV a lot easier. [Jamie] ### **Fixes** @@ -32,8 +22,6 @@ - Made some improvements to the Sonarr Sync job #2127. [Jamie] -- Turn off Server GC to hopefully help with #2127. [Jamie Rees] - - Fixed #2109. [Jamie] - Fixed #2101. [Jamie] @@ -789,3613 +777,3 @@ - Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] -- Fix non-admin rights (#1820) [Rob Gökemeijer] - -- Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] - -- Add the Issue Reporting functionality (#1811) [Jamie] - -- Removed the forum. [tidusjar] - -- #1659 Made the option to ignore notifcations for auto approve. [Jamie] - -- New Crowdin translations (#1806) [Jamie] - -- Fixed a launch issue. [Jamie] - -- Allow users to login without a password. [Jamie] - -- Fixed the emby notifications not being sent. [Jamie] - -- #1802 and other small fixes. [tidusjar] - -- So... This sickrage thing should work now. [tidusjar] - -- Fixed emby connect login issue. [tidusjar] - -- Stop making unnecessary calls to the update service. [Jamie] - -- Fixed a bug where it blocked users with 0 limits. [Jamie] - -- Done #1788. [tidusjar] - -- More logging. [Jamie] - -- Fixed #1738. [Jamie] - -- Fixed build. [Jamie] - -- Fixed the issue where notifications were not sendind unless we restarted #1732. [tidusjar] - -- Fixed an issue with a trailing space in the subdir. [tidusjar] - -- Fixed #1774. [Jamie] - -- #1773. [Jamie] - -- Roll back rxjs (#1778) [bazhip] - -- Fixed build. [Jamie] - -- Fixed #1763. [Jamie] - -- Fix "content length error" on preview gif (#1768) [OoGuru] - -- New preview gif for Ombi V3 README (#1767) [OoGuru] - -- Remove debug code. [tidusjar] - -- Fix #1762. [tidusjar] - -- Fixed the preset themes not loading. [tidusjar] - -- Fixed #1760 and improvements on the auto updater. We may now support windows services... #1460. [Jamie] - -- Fixed #1754. [Jamie] - -- Hide the subject when it's not being used. [Jamie] - -- Error handling #1749. [Jamie] - -- New Crowdin translations (#1741) [Jamie] - -- #1732 #1722 #1711. [Jamie] - -- Fixed an issue with switching the preset themes. [Jamie] - -- Fixed #1743. [Jamie] - -- Fixed #1742. [tidusjar] - -- Fix #1742. [tidusjar] - -- Fixed landing page. [Jamie] - -- Fixed. [Jamie] - -- Translated the Requests page and fixed #1740. [Jamie] - -- Fix crash. [Jamie] - -- Sickrage done. Ish... So i've written all the code by looking at the API. the key there is i've looked at the api. I have not tested anything so expect this to fail. [Jamie] - -- SickRage settings UI. [Jamie] - -- Fixed #1721. [tidusjar] - -- Fixed the preset themes issue. [tidusjar] - -- New Crowdin translations (#1654) [Jamie] - -- Fix build. [Jamie] - -- #1460. [Jamie] - -- Fixed tests. [Jamie] - -- Return css as MIME text/css. [Jamie] - -- More added for the preset themes. [Jamie] - -- Moved around the custom styles. [Jamie] - -- More renames. [Jamie] - -- Renames. [Jamie] - -- Load the first 100 requests. [Jamie] - -- Reduce the memory consumption #1720. [Jamie] - -- Moved the schedules jobs into it's own database, see if it helps with the db locking #1720. [Jamie] - -- Fixed #1712. [tidusjar] - -- Potential fix for #1702. [tidusjar] - -- Fixed #1708. [tidusjar] - -- Fixed #1677. [tidusjar] - -- Fixed build. [tidusjar] - -- Potential fix for the DB locking issue #1720. [tidusjar] - -- #1698. [Jamie] - -- Fixed #1705. [tidusjar] - -- Fixed #1703. [tidusjar] - -- Finished adding preset themes. [Jamie] - -- Fixed #17000. [Jamie] - -- Remove the themes because waiting for a merge from lerams project. [Jamie] - -- Finsihed adding preset themes. [Jamie] - -- Fixed #1677. [Jamie] - -- Temp fix for #1683. [Jamie] - -- Fixed #1685. [Jamie] - -- Lossless Compression of images saves 83 KB (#1676) [Fish2] - -- Fixed the availability checker. [tidusjar] - -- Fixed build. [tidusjar] - -- Push out missing migration. [tidusjar] - -- Potential fix for #1674. [tidusjar] - -- Fixed an issue with the caching. [tidusjar] - -- Fixed telegram #1667. [tidusjar] - -- Fixed #1663. [tidusjar] - -- Should fix #1663. [tidusjar] - -- Stop logged in users going to the login page. [Jamie] - -- Fixed it not updating. Styles should be good now. [Jamie] - -- Re did some of the styling on the movie search page, let me know your thoughts. [Jamie] - -- Fixed #1657. [Jamie] - -- Fixed #1655. [Jamie] - -- Removed authentication resul. [Jamie] - -- New Crowdin translations (#1651) [Jamie] - -- New Crowdin translations (#1648) [Jamie] - -- New Crowdin translations (#1638) [Jamie] - -- Fixed #1644. [Jamie] - -- Moar logs #1643. [tidusjar] - -- Fixed #1640. [tidusjar] - -- Fixed the null ref exception #1460. [tidusjar] - -- Fixed landing page. [TidusJar] - -- Fixed #1641. [TidusJar] - -- Fixed #1641. [TidusJar] - -- New Crowdin translations (#1635) [Jamie] - -- Fixed #1631 and improved translation support Included startup args for the auto updater #1460 Mark TV requests as available #1632. [tidusjar] - -- Remove 32bit. [Jamie] - -- More 32bit support. [Jamie] - -- We now show "Available" for tv shows that is fully available #1602. [tidusjar] - -- Fixed the issue where we have got an episode but not the related series. #1620. [tidusjar] - -- Fixed the dropdown not working on iOS in the settings #1615. [tidusjar] - -- Fixed sonarr not monitoring the latest season #1534. [tidusjar] - -- Fixed the issue with firefox #1544. [tidusjar] - -- Fixed discord #1623. [tidusjar] - -- Add browserstack thanks (#1627) [Matt Jeanes] - -- Fix the exception #1613. [Jamie] - -- Found where we potentially are setting a new poster path, looks like the entity was being modified and being set as Tracked by entity framework, so the next time we called SaveChangesAsync() it would save the new posterpath on the entity. [Jamie] - -- Small modifications. [Jamie] - -- Fixed #1622. [Jamie] - -- Various improvements to webpack/gulp/vscode support (#1617) [Matt Jeanes] - -- Episodes in requests are now in order #1597 (#1614) [masterhuck] - -- Fixed a null reference issue in the Plex Content Cacher. [Jamie.Rees] - -- Fixed #1610. [tidusjar] - -- Really fixed the build this time. [tidusjar] - -- Fixed build. [tidusjar] - -- Made the updater work again #1460. [tidusjar] - -- Adding logging into the auto updater and also added more logging around the create inital user for #1604. [tidusjar] - -- Fixed the issue where we did not check if they are already in sonarr when choosing certain options #1540. [tidusjar] - -- We can now delete tv child requests and the parent will get remove #1603. [tidusjar] - -- Finished the api changes requested #1601. [tidusjar] - -- Fixed the Hangfire server timeout issue #1605. [tidusjar] - -- Fixed notifications not sending #1594. [tidusjar] - -- Fixed #1583 you can now delete users. Fixed the issue where the requested by was not showing. Finally fixed the broken poster paths. [tidusjar] - -- Fixed the issue where movie requests were no longer being requested. [tidusjar] - -- Started adding some more unit tests #1596. [Jamie.Rees] - -- #1588 When we make changes to any requests that we can trigger a notification, always send it to all notification agents, even if the user wont recieve it. [Jamie.Rees] - -- Add a message when email notifications are not setup when requesting a password reset. #1590. [Jamie.Rees] - -- Removed text that we no longer need. [Jamie.Rees] - -- Fixed #1574. [Jamie.Rees] - -- #1460 looks like the permissions issue has been resolved. Just need to make sure the Ombi process is terminated. [Jamie.Rees] - -- Put back the old download code. [Jamie.Rees] - -- Test. [Jamie] - -- Build sln. [Jamie.Rees] - -- Order by the username #1581. [Jamie.Rees] - -- Remove sonarr episodes from the cache table. [Jamie.Rees] - -- Couchpotato finished. [tidusjar] - -- Disable run import button if no import options are selected. [tidusjar] - -- Fixed #1574. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixes the issue with non windows systems unable to unzip the tarball #1460. [tidusjar] - -- Finished the couchpotato settings. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed #1570 #1571. [tidusjar] - -- Fixed #1547. [tidusjar] - -- Should fix #1538. [tidusjar] - -- Fixed #1553. [tidusjar] - -- Fixed #1546. [tidusjar] - -- Fixed #1543. [tidusjar] - -- Fixes an issue with Movie caching not working on develop branch of Radarr (#1567) [Jeffrey Peters] - -- This adds two fields to the Email Notifications settings page. It allows for the disabling of TLS/SSL as well as the ability to disable certificate validation when sending notification emails. (#1552) [Jeffrey Peters] - -- Fixed typo (#1551) [Codehhh] - -- Use Sqlite storage for Hangfire. [tidusjar] - -- Fixed the overrides #1539 also display it on screen now too. [tidusjar] - -- Fixed #1542 also added VSCode support. [tidusjar] - -- Fixed some cosmetic issues #865. [Jamie.Rees] - -- Fixed #1531. [Jamie.Rees] - -- Small fixes #865. [Jamie.Rees] - -- Some errors fixed and some ui improvements #865. [tidusjar] - -- Auto-scale large images down to container size (#1529) [Avi] - -- Fix logo on login page. (#1528) [Avi] - -- Another potential issue? :/ [tidusjar] - -- Real fix. [tidusjar] - -- #1513 Added storage path. [Jamie.Rees] - -- Fixed the discord issue relating to images #1513. [Jamie.Rees] - -- Fixed the issue sending movies to Radarr #1513 Fixed typo #1524. [Jamie.Rees] - -- Fixed logo on reset password pages fixed the run importer button on the user management settings. [Jamie.Rees] - -- Fixed crash/error #865. [tidusjar] - -- #1513 fixed the landing page and also the reverse proxy images. [tidusjar] - -- #1513 correctly set the child requests as approved. [tidusjar] - -- Fixed an issue that potentially causes as issue when siging into plex #865. [tidusjar] - -- Remove dev branch. [PotatoQuality] - -- Prepare readme for upcoming beta. [PotatoQuality] - -- #1513 partially fixed a bug. [tidusjar] - -- Fixed the exception. [tidusjar] - -- Fixed the application url not saving #1513. [tidusjar] - -- Fixed liniting. [tidusjar] - -- REVERSE PROXY BITCH! #1513. [tidusjar] - -- Fixed a bug where we were marking the wrong episodes as available #1513 #865. [Jamie.Rees] - -- Fixed an issue where we messed up the pages and routing. [Jamie.Rees] - -- Emby user importer is now therer! #1456. [tidusjar] - -- #1513 Added the update available icon. [tidusjar] - -- Fixed the issue of it showing as not requested when we find it in Radarr. Made the tv shows match a bit more to the movie requests Added the ability for plex and emby users to login Improved the welcome email, will only show for users that have not logged in Fixed discord notifications the about screen now checks if there is an update ready #1513. [tidusjar] - -- Support email addresses as usernames #1513. [Jamie.Rees] - -- Link to issue treath. [PotatoQuality] - -- Give correct feedback when testing email notifications #1513. [Jamie.Rees] - -- Report issue removed and the deny dropdown removed #1513. [Jamie.Rees] - -- #1513 removed the discord text when testing pushbullet. [Jamie.Rees] - -- Made a lot of changes around the notifcations to support the custom app name also started on the welcome email ##1456. [Jamie.Rees] - -- Fixed the bug where we were displaying shows where we do not have enough information to request #1513. [Jamie.Rees] - -- #1513 added the network to tv shows. [Jamie.Rees] - -- Fixed the whitespace issue #1513. [Jamie.Rees] - -- Fixed the swagger endpoint #865 #1513 Fixed the custom image issue on the login page Fixed the bug when clicking on the tab on the requests page it would switch to the wrong one Swagger is now back @ /swagger. [tidusjar] - -- Optimized images, Update old compressed image with a new lossless one. (#1514) [camjac251] - -- #1513 #865 Fixed the issue where we do not send the requests to Radarr/Sonarr when approving. [tidusjar] - -- #1506 #865 Fixed an issue with the test buttons not working correctly. [tidusjar] - -- #865 Added donation link. [tidusjar] - -- Fixed a bunch of issues on #1513. [tidusjar] - -- #1460 Added the Updater, it all seems to be working correctly. #865. [Jamie.Rees] - -- Removed percentage. [Jamie.Rees] - -- Fixed linter. [Jamie.Rees] - -- Fixed some bugs in the UI #865. [Jamie.Rees] - -- Improved the search buttons #865. [Jamie.Rees] - -- More logging #865. [Jamie.Rees] - -- Made build faster. [Jamie.Rees] - -- More logging. [Jamie.Rees] - -- Set debug level to Debug for now. [Jamie.Rees] - -- Add linting and indexes for interfaces/services (#1510) [Matt Jeanes] - -- Fixed the issue with the tv search not working #1463. [Jamie.Rees] - -- Latest practices... also probably broke some styles - sorry (#1508) [Matt Jeanes] - -- Build with the branch version. [tidusjar] - -- Build fix. [tidusjar] - -- Fixed build. [tidusjar] - -- Omgwtf so many changes. #865. [tidusjar] - -- Tests. [Jamie.Rees] - -- #1456 Started on the User Importer Also added the remember me button. [Jamie.Rees] - -- Made some UI changes, reworked the Emby and Plex screens to make them more user friendly and no so fugly. #865 Also made the login page placeholder text slightly lighter. [Jamie.Rees] - -- Cake skip verification build stuff #865. [Jamie.Rees] - -- Some fixes around the UI and managing requests #865. [tidusjar] - -- #1486. [Jamie.Rees] - -- #1486. [Jamie.Rees] - -- Upgraded to .net core 2.0 #1486. [Jamie.Rees] - -- #865 Finished the landing page, we now check the server's status. [Jamie.Rees] - -- Fixed build. [TidusJar] - -- Removed the telegram api. [Jamie.Rees] - -- Small changes on the updater #1460 #865. [Jamie.Rees] - -- Remove unused functions. [Dhruv Bhavsar] - -- Make Episode picker similar to Requests Child view. #1457 #1463. [Dhruv Bhavsar] - -- Fix merge conflict for TvRequests component. [Dhruv Bhavsar] - -- Upstream Changes... [Dhruv Bhavsar] - -- Clean up Requests page code by moving children request to old component, remove additional REST calls when merging and update component names to make more sense. [Dhruv Bhavsar] - -- Lots of different UI enhancements and fixes #865. [tidusjar] - -- Gitchangelog. [tidusjar] - -- Fixed the issue where we were using the wrong availability options. [tidusjar] - -- Fixed a bunch of bugs in Ombi #865. [tidusjar] - -- Build versioning. [Jamie.Rees] - -- #1460 The assembly versioning seems to work correctly now. [Jamie.Rees] - -- More build versioning changes #865. [tidusjar] - -- Fixed cake script. [Jamie.Rees] - -- WIP on the build versioning for the Updater #1460 #865. [Jamie.Rees] - -- Versioning. [Jamie.Rees] - -- Package versions. [Jamie.Rees] - -- #1460 #865 working on the auto updater. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Small changes around the roles #865. [tidusjar] - -- Improvements to the UI and also finished the availability checker #865 #1464. [Jamie.Rees] - -- Availability Checker #1464 #865. [Jamie.Rees] - -- Fixed ##1492 and finished the episode searcher for #1464. [Jamie.Rees] - -- #1464. [tidusjar] - -- Reload the settings #1464 #865. [Jamie.Rees] - -- #1464 added the Plex episode cacher #865. [Jamie.Rees] - -- Fixed some issues around the tv requests area Added mattermost and telegram notifications #1459 #865 #1457. [tidusjar] - -- Fix global.json. [Dhruv Bhavsar] - -- Working UI for Requests. Approval/Deny does not work as it doesn't in your code either. [Dhruv Bhavsar] - -- Enable diagnostic on build #865. [Jamie.Rees] - -- Fixed the user token issue #865. [Jamie.Rees] - -- Some small refresh token work #865. [Jamie.Rees] - -- Initial TV Requests UI rebuild. [Dhruv Bhavsar] - -- Made a start on supporting multiple emby servers, the UI needs rework #865. [Jamie.Rees] - -- #865 #1459 Added the Sender From field for email notifcations. We can now have "Friendly Names" for email notifications. [Jamie.Rees] - -- Redirect to the landing page when enabled #1458 #865. [Jamie.Rees] - -- Removed IdentityServer, it was overkill #865. [Jamie.Rees] - -- Fixed another bug with identity. #865 I'm thinking about removing it. Causing more hassle than it's worth. [tidusjar] - -- #1460 #865. [tidusjar] - -- Delete appveyor_old.yml. [Jamie] - -- Fixed path. [Jamie.Rees] - -- Silent build level. [Jamie.Rees] - -- #1459 Forgot to get the Pushbullet agent to look up the pusbullet templates rather than the Discord ones. Updated the Gitchange log. [Jamie.Rees] - -- Made the placeholder color on the login page a bit lighter #865. [Jamie.Rees] - -- Landing and login page changes #865 #1485. [tidusjar] - -- #1458 #865 More work on landing. [Jamie.Rees] - -- Working on the landing page #1458 #865. [tidusjar] - -- A lot of clean up and added a new Image api #865. [Jamie.Rees] - -- Cleaned up the Logging API slightly #1465 #865. [Jamie.Rees] - -- Fixed the Identity Server discovery bug #1456 #865. [tidusjar] - -- Fixed the issue with the Identity Server running on a different port, we can now use -url #865. [Jamie.Rees] - -- Try again. [TidusJar] - -- Publish ubuntu 16.04. [Jamie.Rees] - -- Chnaged the updater job from Minutely to Hourly. [Jamie.Rees] - -- Some work around the Auto Updater and other small changes #1460 #865. [Jamie.Rees] - -- Missed a file. [tidusjar] - -- Fixed the swagger issue. [tidusjar] - -- RDP issues. [tidusjar] - -- Appveyor build rdp investigation. [tidusjar] - -- Working on the requests page #1457 #865. [tidusjar] - -- Made the password reset email style the same as other email notifications #1456 #865. [Jamie.Rees] - -- Fixed some bugs around the authentication #1456 #865. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Fixed build #1456. [Jamie.Rees] - -- #1456 #865 Started on allowing Plex Users to sign in through the new authentication server. [Jamie.Rees] - -- Removed covalent. [Jamie.Rees] - -- #1456 Reset Password stuff #865. [Jamie.Rees] - -- Finished implimenting Identity with IdentityServer4. #865 #1456. [Jamie.Rees] - -- Moved over to using Identity Server with Asp.Net Core Identity #1456 #865. [Jamie.Rees] - -- Started on the requests rework #865. [Jamie.Rees] - -- Extended the Emby API. [Jamie.Rees] - -- Started reworking the usermanagement page #1456 #865. [tidusjar] - -- Lots of refactoring #865. [Jamie.Rees] - -- Created an individual user api endpoint so we can make the user management pages better #865. [TidusJar] - -- Lot's of refactoring. [Jamie.Rees] - -- #1462 #865 Had to refactor how we use notificaitons. So we now have more notification fields about the request. [Jamie.Rees] - -- Looks like Sonarr is finished and works. A lot simplier this time around. #865. [tidusjar] - -- More work on the Sonarr Api Integration #865. [tidusjar] - -- Started on sonarr #865. [tidusjar] - -- Small changes #865. [tidusjar] - -- Damn son. So many changes... Fixed alot of stuff around tv episodes with the new DB model #865. [tidusjar] - -- Fixed the TV Requests issue #865. [Jamie.Rees] - -- Fixed a load of bugs need to figure out what is wrong with tv requests #865. [tidusjar] - -- #865 rework the backend data. Actually use real models rather than a JSON store. [Jamie.Rees] - -- Fixed the build issue #865. [tidusjar] - -- Allow us to use Emby as a media server. [tidusjar] - -- More Update #865. [Jamie.Rees] - -- Deployment changes. [Jamie.Rees] - -- More work on the Updater. [Jamie.Rees] - -- Lots of fixes. Becoming more stable now. #865. [tidusjar] - -- Small fixes around the searching. [Jamie.Rees] - -- Some rules #865. [Jamie.Rees] - -- Oops. [TidusJar] - -- Started on the Discord API settings page. [TidusJar] - -- Email Notifications are now fully customizable and work! #865. [Jamie.Rees] - -- Small changes and fixed some stylingon the plex page #865. [Jamie.Rees] - -- More on #865 TODO, Find out whats going on with the notifications and why exceptions are being thrown. [Jamie.Rees] - -- Oops. [Jamie.Rees] - -- Ok #865 fixed the published exe. [Jamie.Rees] - -- Fixed errors. [Jamie.Rees] - -- Fixed build script. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Some more #865. [Jamie.Rees] - -- Create appveyor.yml. [Jamie] - -- The Approving child requests now work! [Jamie.Rees] - -- Fixed many bugs #865. [Jamie.Rees] - -- Loads of changes, improved the movie search stylings is back. [Jamie.Rees] - -- Moved to webpack and started on new style. [Jamie.Rees] - -- Fixed the TV search via Trakt not returning Images anymore. #865. [Jamie.Rees] - -- Rules changes and rework. [Jamie.Rees] - -- Request Grid test. [Jamie.Rees] - -- Small cleanup #865. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Started the Radarr Settings #865. [Jamie.Rees] - -- Massive amount of rework on the plex settings page. It's pretty decent now! #865. [tidusjar] - -- Fixed build. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Rules #865. [tidusjar] - -- Stuff. [Jamie.Rees] - -- Forgot to uncomment. [Jamie.Rees] - -- Tetsd. [Jamie.Rees] - -- Build task changes. [Jamie.Rees] - -- Adsa. [Jamie.Rees] - -- Appveyor. [Jamie.Rees] - -- Stuff around tokens and also builds. [Jamie.Rees] - -- Finished the Plex Content Cacher. Need to do the episodes part but things are now showing as available! #865. [tidusjar] - -- Small user changes #865. [Jamie.Rees] - -- Stuff #865 need to work on the claims correctly. [Jamie.Rees] - -- Reworked the TV model AGAIN #865. [Jamie.Rees] - -- The move! [Jamie.Rees] - -- Fixed build #865. [Jamie.Rees] - -- Fixed the user management #865. [Jamie.Rees] - -- #865 Added support for multiple plex servers. [Jamie.Rees] - -- Bleh. [tidusjar] - -- Small changes. [Jamie.Rees] - -- Fixed the build. [Jamie.Rees] - -- Fixes. [Jamie.Rees] - -- Bundling changes. [Jamie.Rees] - -- Some series information stuff, changes the pace theme too. [Jamie.Rees] - -- Docker support and more, redesign the episodes. [tidusjar] - -- Stuff around episode/season searching/requesting. [Jamie.Rees] - -- Removed redundant folders. [tidusjar] - -- Lots of backend work. [tidusjar] - -- Fixed build. [Jamie.Rees] - -- TV Request stuff. [Jamie.Rees] - -- Work around the user management. [tidusjar] - -- More. [Jamie.Rees] - -- Lots and Lots of work. [Jamie.Rees] - -- Diagnostic changes. [tidusjar] - -- Fixed hangfire exception. [tidusjar] - -- Remove xunit. [tidusjar] - -- Lots more work :( [Jamie.Rees] - -- More changes. [tidusjar] - -- #865. [Jamie.Rees] - -- Small changes. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- More mapping. [Jamie.Rees] - -- Mapping mainly. [Jamie.Rees] - -- Fix systemjs config not being included. [Matt Jeanes] - -- Fixed bundling and various improvements. [Matt Jeanes] - -- Finished the emby wizard #865. [tidusjar] - -- Finished the wizard #865 (For Plex Anyway) [tidusjar] - -- Small changes. [tidusjar] - -- More work on Wizard and Plex API #865. [tidusjar] - -- Settings. [Jamie.Rees] - -- Settings for Ombi. [Jamie.Rees] - -- Fixed some issues around the identity. [Jamie.Rees] - -- #865 more for the authentication. [tidusjar] - -- Auth. [Jamie.Rees] - -- More on the search and requests page. It's almost there for movies. Need to add some filtering logic #865. [tidusjar] - -- #865. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Messing around with the settings. [tidusjar] - -- Fixed the yml. [Jamie.Rees] - -- Remove unneeded bundle config. [Matt Jeanes] - -- Redo dotnet publish targets. [Jamie.Rees] - -- Bundling changes. [Jamie.Rees] - -- Stuff. [Jamie.Rees] - -- Move app into wwwroot. [Jamie.Rees] - -- Put uglify back in! [Jamie.Rees] - -- Wrong line. [Jamie.Rees] - -- Matt is helping. [Jamie.Rees] - -- Revert. [tidusjar] - -- Small tweaks. [tidusjar] - -- Upgrade to .Net Standard 1.6. [tidusjar] - - -## v2.2.1 (2017-04-09) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the forums. [tidusjar] - -- Updates. [tidusjar] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added a retry policy around the emby newsletter. [Jamie.Rees] - -### **Fixes** - -- Revert "Merge branch 'DotNetCore' into dev" [tidusjar] - -- More borken build. [Jamie.Rees] - -- Started adding requesting. [Jamie.Rees] - -- Done the movie searching. [tidusjar] - -- #865. [tidusjar] - -- More. [tidusjar] - -- Moar. [tidusjar] - -- Small changes. [tidusjar] - -- Styling. [Jamie.Rees] - -- MOre changes. [Jamie.Rees] - -- Spacing. [Jamie.Rees] - -- Try again. [Jamie.Rees] - -- More. [Jamie.Rees] - -- Again. [Jamie.Rees] - -- Anbother. [Jamie.Rees] - -- Another. [Jamie.Rees] - -- Another. [Jamie.Rees] - -- Retry. [Jamie.Rees] - -- A. [Jamie.Rees] - -- Fixed. [Jamie.Rees] - -- Cahnge 2. [Jamie.Rees] - -- Appveyor change. [Jamie.Rees] - -- The start of a new world. [Jamie.Rees] - -- Fixed the migration number and order by the added date for the newsletter #1264. [tidusjar] - -- Forgot this change. [tidusjar] - -- Also fixed the issue for the Emby Newsletter where episodes were not getting added :( [tidusjar] - -- #1264 "They may take our lives, but they'll never take our freedom!" [tidusjar] - -- Finished reworking the Sonarr Integration. Seems to be working as expected, faster and most stable. It's Not A Toomah! [tidusjar] - -- Small bit of work. [Jamie.Rees] - -- Made a start on the new Sonarr integration. [tidusjar] - -- For test emails, if there is no new content then just grab some old data. [tidusjar] - -- Fixed an issue where the emby newsletter was always showing series. [tidusjar] - - -## v2.2.0 (2017-03-30) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added a new setting for the Netflix option, we can now disable it appearing in the search. [tidusjar] - -- Update German Translation. [Marius Schiffer] - -- Added a release notes page, you can access via Admin>Updates>Recent Changes tab. Note to self, need to put better comments in for users to understand! [Jamie.Rees] - -- Added gravitar image. [Jamie.Rees] - -- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] - -- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] - -- Added some logging around API calls. [smcpeck] - -- Changed IEmbyAvailabilityChecker to use IEnumberables + checking actor search against Emby content + PR feedback. [smcpeck] - -- Changed actor searching to support non-actors too. [smcpeck] - -- Added a 10 second timer to refresh some new caching I put in. [smcpeck] - -- Added root folder and approving quality profiles in radarr #1065. [tidusjar] - -- Added some debugging code around the newsletter for Emby #1116. [tidusjar] - -- Added a TMDB Rate limiter for the newsletter. [tidusjar] - -- Added port check in wizard. also fixed favicon. [tidusjar] - -- Update Radarr placeholder. [d2dyno] - -- Added the user login for emby users #435. [tidusjar] - -- Added User Management support for Emby #435. [tidusjar] - -- Added emby to the sidebar #435. [tidusjar] - -- Added API endpoint for /actor/new/ to support searching for movies not already available/requested. [smcpeck] - -- Update ISSUE_TEMPLATE.md. [Jamie] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -### **Fixes** - -- Translation changes. [Jamie.Rees] - -- Syntax error. [tidusjar] - -- Fixed an issue where we were retrying the API call when the Plex users login creds were invalid. #1217. [tidusjar] - -- Slightly increased the wait time for the emby newsletter also fixed a potential error in the plex user checker. [Jamie.Rees] - -- Fixed an issue where we were not notifiying emby users. [Jamie.Rees] - -- Fixed the issue where the recent changes page was not showing the correct date. #1296. [Jamie.Rees] - -- Fixed #1252 (Show the correct user type on the management page for Plex Users) [Jamie.Rees] - -- Fixed the casting error #1292. [Jamie.Rees] - -- Fix test newletter not sending when empty. [Dhruv Bhavsar] - -- Quick fix for email false positive message. ISSUE: #1286. [Dhruv Bhavsar] - -- Fixes around the newsletter. We will now correctly show newly added shows and also newly added episodes. #1163. [tidusjar] - -- Fixed a sonarr deseralization error. [tidusjar] - -- Increased the delay for the Episode information api calls. #1163. [tidusjar] - -- Looks like we were overloading emby with out api calls. [tidusjar] - -- Fixed the root path escaping issue for Radarr too! [tidusjar] - -- Some small backend newsletter changes, we can now detect if there are any movies and/or tv shows, if there are none then we will no longer send out an empty newsletter. [Jamie.Rees] - -- Remoddeled the notificaiton settings to make it easier to add more. This is some techinical changes that no one except me will ever notice :( [Jamie.Rees] - -- Fixed #1234. [Jamie.Rees] - -- A fix to the about page and also started to rework the notification backend slightly to easily add more notifications. [Jamie.Rees] - -- Adding more logging into the Plex Cacher. [Jamie.Rees] - -- #1218 changed the text when we cannot display release notes for dev and EAP branches. [Jamie.Rees] - -- Fix for #1236. [SuperPotatoMen] - -- Tooltips. [Jamie.Rees] - -- #236. [Jamie.Rees] - -- #1102. [Jamie.Rees] - -- Done #1012. [Jamie.Rees] - -- Oops #1134. [Jamie.Rees] - -- Fixed #1121. [Jamie.Rees] - -- Fixed #1210. [Jamie.Rees] - -- Fixed typo #1134. [Jamie.Rees] - -- Fixed #1223. [Jamie.Rees] - -- Another newsletter fix attempt #1163 #1116. [tidusjar] - -- Fixup! Reset the branch on v2.1.0 tag to get to a shared state between dev and Master. [distaula] - -- Fixed a bug in the Plex Newsletter. [tidusjar] - -- Typo. [tidusjar] - -- Fixed around the newsletter and a small feature around the permissions/features (#1215) [Jamie] - -- Fixed #1189. [tidusjar] - -- Fixed #1195. [Jamie.Rees] - -- Fixed #1195. [Jamie.Rees] - -- Fixed #1192. [Jamie.Rees] - -- Fixed issue where we could get null rating keys on Plex. [tidusjar] - -- Needed to treat a 201 as success, too. + removed some commented out code. [Shaun McPeck] - -- Normalized spacing/tabs. [smcpeck] - -- Move local user login to be the first thing checked; renamed old Api variable to PlexApi now that Emby is in play. [smcpeck] - -- Remove all the polling/retry logic around HP requests. This was a problem do to not properly awaiting the initial AddArtist API call being sent to HP. Also fix SetAlbumStatus to use ReleaseId instead of MusicBrainsId (same fix previously applied to AddArtist). [smcpeck] - -- Restore checking of HTTP StatusCode on ApiRequests; remove checking of response.ErrorException. [smcpeck] - -- Reverted (for now) non-200 response handling; added some extra logging. [smcpeck] - -- Tweaked ApiRequest behavior on non-200 responses; think it was breaking login. :-" [smcpeck] - -- Only deserialize response payload in ApiRequest when StatusCode == 200. Will a default return value in other cases cause other issues? [smcpeck] - -- Headphones - added releaseID to generic RequestedModel and passing that through to HP request. Their API doesn't request via the MusicBrainzId. [smcpeck] - -- Fixed #1038. [tidusjar] - -- Fixed a slight issue where we could click the change folders button rather than the dropdown arrow #1189. [tidusjar] - -- Bunch of updater files. [tidusjar] - -- #1163 #117. [tidusjar] - -- Removed some unnecessary 'ConfigureAwait` uses. [smcpeck] - -- Remove meaningless html class from actor searching checkbox. [smcpeck] - -- Fixed an issue where we were not always showing movies from external programs. [tidusjar] - -- Remove extra delay when filtering out existing movies. [smcpeck] - -- Post merge build fixes. [smcpeck] - -- Fix. [tidusjar] - -- Fixed #1177. [tidusjar] - -- Fixed #1152. [tidusjar] - -- Fixed #1123. [tidusjar] - -- Fixed a bug when sending to radarr. [tidusjar] - -- Fixed #1133. [tidusjar] - -- Fixed issues img. [Jamie.Rees] - -- Stop Plex being enabled on the first time installing #1048. [Jamie.Rees] - -- The landing page now works for emby #435. [tidusjar] - -- Fixed #1104. [tidusjar] - -- Fixed #1090. [tidusjar] - -- Fixed #1103. [tidusjar] - -- Small changes. [tidusjar] - -- Break out Mass Email feature into its own tab, upgrade Font Awesome and clean up some comments. [dhruvb14] - -- Fix typo. [Travis Bybee] - -- Fixed #1066. [Jamie.Rees] - -- Fixed broken builds. [Jamie.Rees] - -- Fixed #1083. [Jamie.Rees] - -- #1049. [tidusjar] - -- Fixed #1071. [tidusjar] - -- Fixed #1048 #1081. [tidusjar] - -- #1074. [Jamie.Rees] - -- #1069. [Jamie.Rees] - -- Fix for #1068. [tidusjar] - -- Remove duplciate tv show status. [tidusjar] - -- Some request ui changes. [tidusjar] - -- Removed references to Plex. [tidusjar] - -- Removed plex from the scheduled jobs ui. [tidusjar] - -- First run of the newsletter set it to a test. [tidusjar] - -- Reworked the newsletter for Emby! Need to rework it for Plex and use the new way to do it. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed the mass email, it was only being set to users with the newsletter feature #358. [tidusjar] - -- Removed Plex Request from the notifications. [tidusjar] - -- Finish implementing mass email feature. [dhruvb14] - -- @tidusjar pointed out runtime error!! [dhruvb14] - -- Does not compile, need to get data from UI into nancy somehow and figure out why IMassEmail is not initializing. [dhruvb14] - -- Begin Implementing Mass Email Section. [dhruvb14] - -- Hide the auto update btn #236 Fixed where we were not populating the emby episodes #435. [tidusjar] - -- Fix Radarr labels. [d2dyno] - -- Fixed pace loader. [Jamie.Rees] - -- Check if Emby/Plex is enabled before starting the job. [Jamie.Rees] - -- Fixed #1036. [Jamie.Rees] - -- Fixed a typo and changed wording. [Torkil Liseth] - -- Fixed #1035. [Jamie.Rees] - -- Fix for #1026. [Jamie.Rees] - -- Fixed #1042. [tidusjar] - -- #435. [Jamie.Rees] - -- #435 Started the wizard. [Jamie.Rees] - -- Removed. [tidusjar] - -- Final Fixes. [dhruvb14] - -- Partial fix for broken HR tag's in Email... [dhruvb14] - -- DAMN! #435 that's a lot of code! [tidusjar] - -- Started adding Emby, Lots of backend work done. Need a few more services done and login and user management. #435. [tidusjar] - -- UI changes to add checkbox and support searching for only new matches via new API. [smcpeck] - -- REFACTOR: IAvailabilityChecker - changed arrays to IEnumerables. [smcpeck] - -- UI changes to consume actor searching API. [smcpeck] - -- API changes to allow for searching movies by actor. [smcpeck] - -- Enforcing async/await in synchronous methods that were marked async. [smcpeck] - - -## v2.1.0 (2017-01-31) - -### **New Features** - -- Update README.md. [Jamie] - -- Update .gitattributes. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added the new labels to the search. [tidusjar] - -- Added a switch to use the new search or not, just in case people do not like it. added a migration to turn on the new search. [Jamie.Rees] - -- Added a bunch of categories for tv search similar to what we have for movies. [Jamie.Rees] - -### **Fixes** - -- Fixed typos. [Haries Ramdhani] - -- Fix typo in readme. [tdorsey] - -- Fixed #985. [Jamie.Rees] - -- FIxed #978. [tidusjar] - -- Fixed the approval issue for #939. [tidusjar] - -- Some general improvements. [tidusjar] - -- Turned off migration for now. [tidusjar] - -- Fixed #998. [tidusjar] - -- Additional movie information. [Jamie.Rees] - -- Debug info around the notifications. [Jamie.Rees] - -- Small changes. [tidusjar] - -- Fixed #995. [tidusjar] - -- Fix for #978. [tidusjar] - -- Fixed #991. [tidusjar] - -- Fixed the login issue and pass Radarr the year #990. [Jamie.Rees] - -- More small tweaks around the username/alias. [Jamie.Rees] - -- Possible issue with the empty username. [Jamie.Rees] - -- Potential Fix for #985. [Jamie.Rees] - -- Small changed to the sidebar. [Jamie.Rees] - -- Small changes. [Jamie.Rees] - -- Done #627. [Jamie.Rees] - -- Finished #535 #445 #170. [tidusjar] - -- Fixed tests. [Jamie.Rees] - -- Started to add the specify Sonarr root folders. [Jamie.Rees] - -- Fixed #968. [Jamie.Rees] - -- Fixed #970. [Jamie.Rees] - -- Done #924. [Jamie.Rees] - -- Fixed. [Jamie.Rees] - -- Fixed #955. [Jamie.Rees] - -- #956. [Jamie.Rees] - -- Fixed #947. [Jamie.Rees] - -- #951. [tidusjar] - -- Finished #923 !!! [tidusjar] - -- More for #923. [Jamie.Rees] - -- Radarr integartion in progress #923. [tidusjar] - -- Fixed #940 don't show any shows without a tvdb id. [tidusjar] - -- Finished #739. [Jamie.Rees] - -- Initial impliementation of #739. [Jamie.Rees] - -- Improved the search UI and made it more consistant. Finished the Netflix API Part #884. [Jamie.Rees] - - -## v2.0.1 (2017-01-16) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added a netflix api. [Jamie.Rees] - -### **Fixes** - -- Fixed #934. [Jamie.Rees] - - -## v2.0 (2017-01-14) - -### **New Features** - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Added the settings for #925 but need to apply the settings to the UI. [Jamie.Rees] - -- Changed the settings name from Plex Requests to Ombi. [Jamie.Rees] - -- Added support for Managed Users #811. [Jamie.Rees] - -- Change solution name in travis. [mhann] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -### **Fixes** - -- Finished #925. [Jamie.Rees] - -- Some TODO's. [Jamie.Rees] - -- Fixed #915. [Jamie.Rees] - -- Fixed the issue where notifications are not being sent to users with Aliases #912. [Jamie.Rees] - -- Fixed #891. [Jamie.Rees] - -- Fix indentation issue. [Marcus Hann] - -- Implement simple button. [Marcus Hann] - -- Plex Username Case Sensitivity Fix. [thegame3202] - -- Fixed #882. [Jamie.Rees] - -- Api changed again, so more fixes for #878. [Jamie.Rees] - -- Possible fix for #893. [Jamie.Rees] - -- Fixed #898. [Jamie.Rees] - -- Fixed #878. [TidusJar] - -- * userManagementController.js: fixed #881. [TidusJar] - -- More work on watcher, should all be good now. #878. [Jamie.Rees] - -- Delete PlexRequests.sln.DotSettings. [Jamie] - -- Fixed #862. [Jamie.Rees] - -- #399 and #398 finished. [Jamie.Rees] - -- More work on #399. [Jamie.Rees] - -- Finished #884. [Jamie.Rees] - -- More for #844. [Jamie.Rees] - -- Another #844. [Jamie.Rees] - -- Fixed a dependancy issue with #844. [Jamie.Rees] - -- Finished the main part of #844 just need testing. [Jamie.Rees] - -- Fixed #832. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Fix tiny readme typo. [mhann] - -- Fixed #850 also started #844 (Wrote the API interaction) [Jamie.Rees] - -- #801 #292 done. [Jamie.Rees] - -- Should fix #841 #835 #810. [Jamie.Rees] - -- Fix small typo in ticket overview page. [mhann] - -- More work on the combined login. [Jamie.Rees] - -- Fixed db issue. [Jamie.Rees] - -- Name changes. [Jamie.Rees] - -- All Sln changes. [tidusjar] - -- Moved API Sln dir. [tidusjar] - -- Fixed build. [tidusjar] - -- Moved namespaces. [tidusjar] - -- Renamed zip. [tidusjar] - -- Product name change. [tidusjar] - - -## v1.10.1 (2016-12-17) - -### **Fixes** - -- #788 fixed! [tidusjar] - -- Fixed #788 and #791. [tidusjar] - -- #399 #398. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Small refactorings. [Jamie.Rees] - -- #782. [Jamie.Rees] - -- #785. [Jamie.Rees] - - -## v1.10.0 (2016-12-15) - -### **New Features** - -- Update README.md. [Jamie] - -- Added optional launch args for the auto updater. [Jamie.Rees] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update _Navbar.cshtml. [Jamie] - -- Update README.md. [Jamie] - -- Update _Navbar.cshtml. [Jamie] - -- Update README.md. [Jamie] - -- Added a new permission to bypass the request limit. [Jamie.Rees] - -- Update Version1100.cs. [SuperPotatoMen] - -- Added logging around the Newsletter #717. [Jamie.Rees] - -- Added missing migration. [tidusjar] - -- Added loading spinner. [Jamie.Rees] - -- Update UI.resx. [SuperPotatoMen] - -- Update Version1100.cs. [Jamie] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -### **Fixes** - -- Fixed an issue where the HTML in the newsletter was incorrect. [Jamie.Rees] - -- Fixed #201. [Jamie.Rees] - -- Fixed #720 and added better error handling around the migrations. [Jamie.Rees] - -- Fixed #769. [Jamie.Rees] - -- Write out the actual file version. [Jamie.Rees] - -- Error checking around GA. [TidusJar] - -- Fixed #761. [tidusjar] - -- Fixed #749 Fixed an issue where we were adding the read only permission when creating the admin. [tidusjar] - -- Fixed #757. [tidusjar] - -- Fixes around the server admin #754. [Jamie.Rees] - -- Removed the trace option from the UI, it is only accessible when appending the url with "?developer" #753. [Jamie.Rees] - -- Fixed an issue where the admin could not be updated. [Jamie.Rees] - -- Fixed #745. [Jamie.Rees] - -- Fixed #748. [Jamie.Rees] - -- Workaround for #748. [SuperPotatoMen] - -- Another attempt to fix #717. [tidusjar] - -- Fixed #744. [Jamie.Rees] - -- Tidied up the warnings. [Jamie.Rees] - -- Small bit of analytics. [Jamie.Rees] - -- Removed the whitelist. [Jamie.Rees] - -- Should fix #696 Fixed an issue with the scheduled jobs where it could use a different trigger if the order between the schedules and the triggers were in different positions in the array... Stupid me, ordering both arrays by the name now. [tidusjar] - -- Some better null object handling #731. [Jamie.Rees] - -- Small tweaks. [Jamie.Rees] - -- Fixed #728. [Jamie.Rees] - -- Fixed #727. [Jamie.Rees] - -- Fixed admin redirect issue. [tidusjar] - -- Fixed #718. [tidusjar] - -- Lots of small fixes and tweaks. [Jamie.Rees] - -- Tidied up some of the angular code, split the UI into it's own directives for easier maintainability. [Jamie.Rees] - -- Small tweaks to the Request Page. [Jamie.Rees] - -- Reverted the PR that may have caused #619. [Jamie.Rees] - -- Some small tweaks around #218 Just added the link to the settings and some angular improvements. [Jamie.Rees] - -- Test. [Jamie.Rees] - -- Attempt at fixing #686. [Jamie.Rees] - -- Finished #707. [Jamie.Rees] - -- Fixed #704. [Jamie.Rees] - -- Fixed #705. [Jamie.Rees] - -- Fixed #706. [Jamie.Rees] - -- #547. [Jamie.Rees] - -- Default tabs #304. [Jamie.Rees] - -- Fixed #703. [Jamie.Rees] - -- #233. [Jamie.Rees] - -- Done #678. [Jamie.Rees] - -- Fixed #670. [Jamie.Rees] - -- #456 Update all the requests when we identify that the username changes. [Jamie.Rees] - -- More user management. [Jamie.Rees] - -- #218. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Implimented the features #218. [tidusjar] - -- Use the user alias everywhere if it is set #218. [tidusjar] - -- Implimented auto approve permissions #218. [tidusjar] - -- A. [tidusjar] - -- Fixed an IOC issue. [tidusjar] - -- Fixed the issue with user management, needed to implement our own authentication provider. [Jamie.Rees] - -- Small changes including #666. [Jamie.Rees] - -- Done #679. [tidusjar] - -- Reduce the retry time. [Jamie.Rees] - -- Remove all references to the claims. [Jamie.Rees] - -- Lots of fixed and stuff. [Jamie.Rees] - -- Fixed potential crash #683. [Jamie.Rees] - -- Fixed #681. [Jamie.Rees] - -- More user management. [Jamie.Rees] - -- Finishing off the user management page #218 #359 #195. [Jamie.Rees] - -- Finished #646 and fixed #664. [Jamie.Rees] - -- Started on #646. Fixed #657. [Jamie.Rees] - -- Fixed #665. [Jamie.Rees] - -- Migrate users. [TidusJar] - -- Fixed build. [Jamie.Rees] - -- Convert the for to foreach for better readability. Still need to rework this area. [Jamie.Rees] - -- Final Tweaks #483. [Jamie.Rees] - -- Finished #483. [Jamie.Rees] - -- Finished the queue #483. [Jamie.Rees] - -- Started on the queue for requests #483 TV Requests with missing information has been completed. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Finished the notification for the fault queue. [Jamie.Rees] - -- Finished #556. [Jamie.Rees] - -- Finished #633 (First part of the queuing) [Jamie.Rees] - -- Finished #659 #236 has been modified slightly. Needs testing on Different systems. [Jamie.Rees] - -- Almost finished #659. [Jamie.Rees] - -- Started on #483. [Jamie.Rees] - -- #544. [Jamie.Rees] - -- Fixed #656 and more work on #218. [Jamie.Rees] - -- Fixed some issues with the user management work. [TidusJar] - -- Fixed build issue. [TidusJar] - -- User perms. [Jamie.Rees] - - -## v1.9.7 (2016-11-02) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Potential fix for #629. [TidusJar] - -- Fixed an issue to stop blatting over the base url. [tidusjar] - -- Fixed #643. [TidusJar] - -- Fixed #622. [TidusJar] - - -## v1.9.6 (2016-10-28) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fixed #586. [Jamie.Rees] - -- Fixed #622. [Jamie.Rees] - -- Fixed #621. [Jamie.Rees] - - -## v1.9.5 (2016-10-27) - -### **New Features** - -- Added our own custom migrations, a lot easier to migrate DB versions now. [tidusjar] - -### **Fixes** - -- Bump version. [Jamie.Rees] - -- Small bit of work on the user claims. [Jamie.Rees] - -- Fix #612 again. [Jamie.Rees] - -- User management styling. [Jamie.Rees] - -- Fixed #608 and some other small stuff. [tidusjar] - -- More user mapping. [tidusjar] - -- Fixed #615. [tidusjar] - -- Fixed #610. [tidusjar] - -- User management stuff. [Jamie.Rees] - -- User management work. [Jamie.Rees] - -- Revert the TVSender to use the old code. [Jamie.Rees] - -- Fixed the view issue. [tidusjar] - -- S582: admin improvements part 2. [Jim MacKenzie] - -- Fix #612. [Jamie.Rees] - -- User management, migration and newsletter. [Jamie.Rees] - -- #602 recently added improvements. [tidusjar] - -- Revert "Sorting out the current state of migrations" [Jamie.Rees] - -- Sorting out the current state of migrations. [Jamie.Rees] - -- Marked as obsolete. [Jim MacKenzie] - -- Migration setup. [Jim MacKenzie] - -- Removed extra line breaks. [Jim MacKenzie] - -- Moved Newsletter Settings to its own page. [Jim MacKenzie] - -- Reverted TMDB package. [Jamie.Rees] - -- Remove DB Option. [Jamie.Rees] - -- Upgrade the movie DB package and fixed #370 To fix this I had to make another API call... It slows down the search... [tidusjar] - -- Lots of small fixes including #475. [tidusjar] - -- A better fix for #587. [tidusjar] - -- Fixed #553. [tidusjar] - -- #601. [Jamie.Rees] - -- More rework to use the Plex DB. [Jamie.Rees] - -- More work around using the PlexDatabase. [Jamie.Rees] - -- Plex DB. [Jamie.Rees] - -- Allow to process even know we had an error #578. [Jamie.Rees] - -- Fix boostrapper-datetimepicker imports (#586) [David Torosyan] - -- Potential work around for #587. [tidusjar] - -- Fixed #589. [tidusjar] - - -## v1.9.4 (2016-10-10) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added Paypalme options, no UI yet (#568) [Jim MacKenize] - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -### **Fixes** - -- Reverted. [tidusjar] - -- Make sure it's enabled before sending the recently added. [tidusjar] - -- Moved the HR inside the table for TV Shows. [Jamie.Rees] - -- FIXED!!!!! YES BITCH! #550. [tidusjar] - -- Moved the horizontal rules inside the table row. [tidusjar] - - -## v1.9.3 (2016-10-09) - -### **New Features** - -- Added properties to disable tv requests for specific episodes or seasons and wired up to admin settings. [Matt McHughes] - -- Added different sonarr search commands. [tidusjar] - -### **Fixes** - -- Fixed #515. [tidusjar] - -- Fixed #561 and a small bit of work on #569. [tidusjar] - -- #569. [tidusjar] - -- Fixed case typo. [Matt McHughes] - -- Finished wiring tv request settings to tv search. [Matt McHughes] - -- WIP hide tv request options based on admin settings. [Matt McHughes] - -- Set meta charset to be utf-8. [Madeleine Schönemann] - -- F#552: updated labels text. [Jim MacKenize] - -- F#552: Re-design lables. [Jim MacKenzie] - -- Last correction.. Now the translation is ready to be used. [Michael Reber] - -- Forgot to correct two incorrect translations. [Michael Reber] - -- Correction of the German translation. [Michael Reber] - -- Notification improvements. [tidusjar] - -- #515. [tidusjar] - - -## v1.9.2 (2016-09-18) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update CouchPotatoCacher.cs. [Jamie] - -- Added some error handing around the GetMovie area #517. [tidusjar] - -- Added a version endpoint in "/api/version" #529. [tidusjar] - -### **Fixes** - -- Trying to fix the auto CP. [tidusjar] - -- Increase the notice message text box #527. [tidusjar] - -- #536 this should fix notification settings when it is being unsubscribed when testing. [tidusjar] - -- Improved how the TV search looks and feels. [tidusjar] - -- Fix for reverse proxy when using the wizard. [Devin Buhl] - -- Fixed #532. [tidusjar] - -- This should fix some issues with the episode requests #514. [tidusjar] - -- Small changes around existing series. [tidusjar] - -- Fixed #514 and the unit tests. [tidusjar] - -- If there is a bad password when changing it, we now inform the user. [tidusjar] - -- When logging out as admin remove the username from the session. [tidusjar] - -- Sorted out some of the UI for #18. [tidusjar] - -- Finished #18. [tidusjar] - - -## v1.9.1 (2016-08-30) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added french to the navbar. [tidusjar] - -- Changed the way we use the setTimeout function. Should fix #403 #491 #492. [tidusjar] - -- Change the redirection to use a relative uri redirect #473. [tidusjar] - -### **Fixes** - -- Fixed tests. [tidusjar] - -- Fixed #491 and added more logging around the email messages under the Info level. [tidusjar] - -- Finished #415. [tidusjar] - -- Fixed an issue where there were some JS errors on the landing page settings and stopped us being redirected to the login sometimes as an admin. [tidusjar] - -- Fixed #480. [tidusjar] - -- User management. [tidusjar] - -- Fixed #505. [tidusjar] - -- Append the application version to the end of our JS/CSS files. [tidusjar] - -- Fixed issue #487. [tidusjar] - -- Remove the datetime picker css from the main assets block and only load it on the pages it needs. #493. [tidusjar] - -- Redirect to search if we are already logged in #488. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed an issue where you could set the base url as requests #479. [tidusjar] - -- Working on the beta releases page and also the user management. [tidusjar] - -- User work. [tidusjar] - - -## v1.9.0 (2016-08-18) - -### **New Features** - -- Update the availability checker to search for TV Episodes. [tidusjar] - -- Changed the no TVMazeid message. [tidusjar] - -- Added an option to disable/enable the Plex episode cacher. [tidusjar] - -- Updated the episode cacher to have a minimum of 11 hours before it runs again. [tidusjar] - -- Added some useful analytical infomation around the wizard. [tidusjar] - -- Updated the German translations #402. [tidusjar] - -- Added some code to shrink the DB. reworked the search to speed it up. [tidusjar] - -- Change to use the GrandparentTitle rather than the thumbnail.... facepalm. [tidusjar] - -- Change the interval to hours! [tidusjar] - -- Added the transaction back into the DB. Do not run the episode cacher if it's been run in the last hour. [tidusjar] - -- Added logging. [tidusjar] - -- Changed the query slightly. [tidusjar] - -- Updated Newtonsoft.Json, Autofixture, Nlog and Dapper packages. [tidusjar] - -- Added the Sonarr check for episodes #254. [tidusjar] - -- Added unit tests. [tidusjar] - -- Added #436. [tidusjar] - -- Update build no. [tidusjar] - -- Updated translations for #402. [tidusjar] - -- Added a beta module. [tidusjar] - -- Added a custom debug root path provider, this means we do not have to recompile the views every time we make a view change. [tidusjar] - -- Update .gitignore. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added tests for the string hash. [tidusjar] - -- Added code to request the api key for CouchPotato. [tidusjar] - -- Added the file version to the layout. [tidusjar] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added automation tests. [tidusjar] - -- Update appveyor.yml. [Jamie] - -- Updated packages. [tidusjar] - -- Updated Polly. [tidusjar] - -- Updated Fr, IT and NL translations #402. [tidusjar] - -- Changed the way the donate button works for #414. [tidusjar] - -- Added MediatR. [tidusjar] - -### **Fixes** - -- User management stuff. [tidusjar] - -- Fixed! [tidusjar] - -- Small amount of work on the user management. [tidusjar] - -- Fixed #466. [tidusjar] - -- Fixes. [tidusjar] - -- Made the episode request better. [tidusjar] - -- Removed commented out tests. [tidusjar] - -- Fixed the bad test after the merge. [tidusjar] - -- Reworked #466. [tidusjar] - -- More unit tests around the login and also the core Plex Checker. [tidusjar] - -- Potentially fixed the issue where we were requesting everything that was also available now. [tidusjar] - -- This should fix #466. [tidusjar] - -- Attempt at fixing a potential bug found from #466. [tidusjar] - -- #464 fixed. [tidusjar] - -- Fixed the build. [tidusjar] - -- Small improvements to the wizard. [tidusjar] - -- Always set the wizard to be true when editing the Plex Requests settings (Since the flag is not in the UI, a bool defaults to false). [tidusjar] - -- Tiny bit of more info. [tidusjar] - -- Finished #459. [tidusjar] - -- #459 is almost done. [tidusjar] - -- Modified the episode modal so that we are now resetting the button after a request. [tidusjar] - -- Commented out the transaction for now to debug it. [tidusjar] - -- Since we are multithreading, we should use a threadsafe type to store the episodes to prevent any threading or race conditions. [tidusjar] - -- Wrapped the bulk insert inside a transaction. [tidusjar] - -- Made the episode check parallel. [tidusjar] - -- Log out the GUID causing the issue. [tidusjar] - -- Fixed another test. [tidusjar] - -- Fixed tests. [tidusjar] - -- Got mostly everything working for #254 Ready for testing. [tidusjar] - -- Fixed issue with saving to db. [tidusjar] - -- Need to work out why the cacher is not working and where the datatype mismatch is. [tidusjar] - -- Don't delete first. [tidusjar] - -- Fix the log path issue #451. [tidusjar] - -- Dump an item. [tidusjar] - -- Small change with the return value in the batch insert. [tidusjar] - -- #254 Removed the cache, we are now storing the plex information into the database. [tidusjar] - -- Small change in the episode saver. [tidusjar] - -- Some small tweaks to improve the memory alloc. [tidusjar] - -- Short circuit when Plex hasn't been setup. Added Miniprofiler. [tidusjar] - -- Consolidate newtonsoft.json packages. [tidusjar] - -- Some performance improvements around the new TV stuff. [tidusjar] - -- Reworked the cacher, fixed the memory leak. No more logging within tight loops. [tidusjar] - -- Another null check. [tidusjar] - -- Some more changes. [tidusjar] - -- Some error handling. [tidusjar] - -- Check if the sonarr ep is monitored. [tidusjar] - -- Some logging. [tidusjar] - -- Small changes, we will actually see the episode cacher on the scheduled jobs page now. [tidusjar] - -- Work on the UI to show what episodes have been requested #254. [tidusjar] - -- Small fix. [tidusjar] - -- Fix the api change in #450. [tidusjar] - -- #254. [tidusjar] - -- Workaround for #440. [tidusjar] - -- Async async async improvements. [tidusjar] - -- Finished #266 Added a new cacher job to cache all episodes in Plex. [tidusjar] - -- Fixed #442. [tidusjar] - -- #254. [tidusjar] - -- Work around the sonarr bug #254. [tidusjar] - -- #254 having an issue with Sonarr. [tidusjar] - -- Small bit of work on #266. [tidusjar] - -- #254. [tidusjar] - -- Precheck and disable the episode boxes if we already have requested it. TODO check sonarr to see if it's already there. #254. [tidusjar] - -- Fixed broken build. [tidusjar] - -- More work for #254. [tidusjar] - -- More work on #254. [tidusjar] - -- Fixed the bug in #438 and added unit tests to make so we dont break it in the future. [tidusjar] - -- Some reason we had dupe translations. [tidusjar] - -- Rename SubDir to Base Url. [tidusjar] - -- Fix the exception in #440. [tidusjar] - -- Reworking the login page for #426. [tidusjar] - -- Fixed #438. [tidusjar] - -- Finished the auth stuff. [tidusjar] - -- Finished up the SMTP side of #429. [tidusjar] - -- #428 Added a message when the we cannot get a TVMaze ID. [tidusjar] - -- #254 MOSTLY DONE! At last, this took a while. [tidusjar] - -- Removed the other rootpath provider. [TidusJar] - -- Removed the other rootpath provider. [TidusJar] - -- Removed the other rootpath provider. [TidusJar] - -- Should fix #429. [TidusJar] - -- Done #135 We are including the application version number in the directory. [tidusjar] - -- #387 trim the spaces from the api key. Tidied up the setting models a bit. [tidusjar] - -- Wrapped the repo to catch Sqlite corrupt messages. [tidusjar] - -- Frontend and tv episodes api work for #254. [tidusjar] - -- #424. [tidusjar] - -- #359. [tidusjar] - -- Moved the plex auth token to the plex settings where it should belong. [tidusjar] - -- Small changes around the user management. [tidusjar] - -- Missed brace. [tidusjar] - -- Removed. [tidusjar] - -- Fixed issues from the merge. [tidusjar] - -- Stupid &$(*£ merge. [tidusjar] - -- Angular. [tidusjar] - -- Reworked the custom notifications... again. Need to figure out how to find the view to the model. [tidusjar] - -- Fixed #417. [tidusjar] - -- Removed NinjectConventions, we hadn't started to use it anyway. [tidusjar] - -- Fixed the way we will be using custom messages. [tidusjar] - -- Test checkin. [tidusjar] - -- Better handling for #388. [tidusjar] - -- Fixed #412. [tidusjar] - -- Fixed #413. [tidusjar] - -- Fixed #409. [tidusjar] - -- Trycatch around the availbility checker. [tidusjar] - -- WIP on notification resolver. [tidusjar] - -- Tidy. [tidusjar] - -- Plugged in MediatR. [tidusjar] - -- Moved over to using Ninject. [tidusjar] - - -## v1.8.4 (2016-06-30) - -### **Fixes** - -- Fixed the bug where we were auto approving everything. Added French language into the navigation bar. [tidusjar] - - -## v1.8.3 (2016-06-29) - -### **New Features** - -- Update README.md. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added some of the backend bits for #182. [tidusjar] - -- Updates for #243. [tidusjar] - -- Added Dutch language #243. [tidusjar] - -- Added languages #243. [tidusjar] - -- Added logging #350. [tidusjar] - -### **Fixes** - -- Small changes. [tidusjar] - -- Allow html in the notice message. [tidusjar] - -- Some more unit tests around the NotificationMessageResolver. [tidusjar] - -- Fixed a timing bug found the in build. Note, when working with time differences use TotalDays. [tidusjar] - -- More translations on the search page (Mainly the notification messages) #243. [tidusjar] - -- Fixed some warnings. [tidusjar] - -- CodeCleanup. [tidusjar] - -- Fixed a bit of a stupid bug in the resetter and added unit tests around it to make sure this never happens again. [tidusjar] - -- Fixed an issue where we didn't provide the correct response when clearing the logs. [tidusjar] - -- Made it so users that are in the whitelist do not have a request limit. [tidusjar] - -- Made it so the request limit doesn't apply to admin users. [tidusjar] - -- Fixed where a user could see the delete button on the issues page. [tidusjar] - -- Fixed some small issues and improved the navbar. [tidusjar] - -- Translated the Requested page #243. [tidusjar] - -- Finished #337. [tidusjar] - -- Some analytics. [tidusjar] - -- More translations for #243 and welcome text for #293. [tidusjar] - -- Small bit of work for #359. [tidusjar] - -- Finished #6. [tidusjar] - -- Analytics and fixes. [tidusjar] - -- Translated the search page #243. [tidusjar] - -- Implemented the different languages and added the ability to change cultures. #243. [tidusjar] - -- Started #243. [tidusjar] - -- Fixed #364. [tidusjar] - -- Some more useful analytical information. [tidusjar] - -- Generic try catch to fix #350. [tidusjar] - -- Slight changes, moved the donate button. [tidusjar] - -- Potential fix for #350. [tidusjar] - -- Better way of obtaining clean enum string. [Drewster727] - -- Fixed #362. [tidusjar] - - -## v1.8.2 (2016-06-22) - -### **New Features** - -- Update readme. [tidusjar] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fixed a circular reference issue. [tidusjar] - -- Small changes around how we work with custom events in the analytics. [tidusjar] - -- Fixed #353 #354 #355. [tidusjar] - -- Null provider check for movies. [Drewster727] - -- Show request type in notifications #346 and fix an issue from previous commit for #345. [Drewster727] - -- Add an option to stop sending notifications for requests that don't require approval #345. [Drewster727] - - -## v1.8.1 (2016-06-21) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fix obj ref error when scheduler runs (ProviderId is null?) [Drewster727] - -- Fix logic for obtaining a sonarr quality profile #340. [Drewster727] - - -## v1.8.0 (2016-06-21) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the new advanced search into the search page too. [tidusjar] - -- Change the way we configure the IoC container in the bootstrapper, we are registering all the concrete instances on application start rather than on each web request. This should increase the performance per HTTP request. [tidusjar] - -- Updated nlog and fixed #295. [tidusjar] - -### **Fixes** - -- Workaround for #334. [Drewster727] - -- Create .gitattributes. [Jamie] - -- Fixes to the issues. [tidusjar] - -- Set the defaults for the landing page. [tidusjar] - -- Revert branch to 664dae2. [tidusjar] - -- Some unit tests for the issues. [tidusjar] - -- Tidied up the bootstrapper. [tidusjar] - -- Fix up landing page UI. [Drewster727] - -- Fixed CSS issue with the top arrow in the Plex theme. [tidusjar] - -- Small changes. [tidusjar] - -- Done #318. [tidusjar] - -- Fixed tests. [tidusjar] - -- #298 added some tests for the landing page. [tidusjar] - -- We are now only keeping the latest 1000 log records in the database. Delete everything else. [tidusjar] - -- Some analytic stuff. [tidusjar] - -- Capture the TVDBID when requesting. [tidusjar] - -- Attempting to improve #219. [tidusjar] - -- Just some more async changes. [tidusjar] - -- Small changes. [tidusjar] - -- More work on #298. Everything wired up. [tidusjar] - -- Fixed the issue on the landing page #298. [tidusjar] - -- #298 moved the content to the left a bit. [tidusjar] - -- Styling for #298 done, just need to wire up the model and do the actual status check. [tidusjar] - -- Bumped up the version number. [tidusjar] - -- Removed some DumpJson() from the trace logs. [tidusjar] - -- Small ui fix (100% width user/password fields to improve mobile experience) [Drewster727] - -- Landing page stuff #298. [tidusjar] - -- Datepicker UI fixes + small landing page UI fix. [Drewster727] - -- Removed a change that shoudn't have been commited. [tidusjar] - -- Fixed tests. [tidusjar] - -- More work for #298. [tidusjar] - -- #273 added for only available content on the search. [tidusjar] - -- Fixed #303 Looks like there was some incorrect business logic. [tidusjar] - -- Most of #273 done. [tidusjar] - -- Settings done for #298. [tidusjar] - -- Started #298. [tidusjar] - -- A crap tonne of work on #273. [tidusjar] - -- More work on #273. [tidusjar] - -- Reduced kept logs for 2 days. [tidusjar] - -- Fixed #300. [tidusjar] - -- #273. [tidusjar] - -- Fixed a bug with some users with the CP profiles. [tidusjar] - -- #273. [tidusjar] - -- Done the same for TV. [tidusjar] - -- Fixes #296. [tidusjar] - -- More for #273. [tidusjar] - -- Small changes. [tidusjar] - -- Revert "Small changes" [tidusjar] - -- Small changes. [tidusjar] - -- Finished #221 and added more async #278. [tidusjar] - -- Spelling mistake in the html! this fixes #264. [tidusjar] - -- More work on #273. [tidusjar] - -- Fixed #210. [tidusjar] - -- Started #273. [tidusjar] - - -## v1.7.5 (2016-05-29) - -### **New Features** - -- Update preview. [Jamie] - -- Updated dapper.contrib. Looks like there was a bug in the async methods. [tidusjar] - -- Updater wouldn't work when running a reverse proxy #236. [tidusjar] - -### **Fixes** - -- Bump build ver. [tidusjar] - -- Use HTTPS for the poster images, so there aren't any mixed content warnings when serving the application via an HTTPS reverse proxy. [Sean Callinan] - -- Removed static declarations. [tidusjar] - -- Fixed styling on modal. [tidusjar] - -- Made the search page all async goodness #278. [tidusjar] - -- Made the request module async #278. [tidusjar] - -- Started some dynamic scrolling. [tidusjar] - -- Stop dumping out the settings to the log. [tidusjar] - -- Made more async goodness. [tidusjar] - -- Made some of the searching async #278. [tidusjar] - -- Fixed #277. [tidusjar] - -- Reworked some tests. [tidusjar] - -- #26q make the auth users list taller. [Drewster727] - -- Fix 404 error. [Drewster727] - -- #262 make the auth users list taller. [Drewster727] - -- #221 delete requests per category. [Drewster727] - -- #256 #237 UI Improvements and consolidation. [Drewster727] - -- Fixed a bug in the user notification where if an admin wants to be notified they wouldn't be. [tidusjar] - -- Set the admin to have all claims. [tidusjar] - -- Fix null exception possibility in cp/sickrage cacher classes. [Drewster727] - -- Fixed #244. [tidusjar] - -- Fixed #240. [tidusjar] - -- Fixed #270. [tidusjar] - -- Fixed an issue where if you have only 1 plex friend it would not show in the list. [tidusjar] - - -## v1.7.4 (2016-05-25) - -### **New Features** - -- Update README.md. [Jamie] - -### **Fixes** - -- Fixed #252. [tidusjar] - -- Fixed #428. [tidusjar] - -- Version bump. [tidusjar] - -- Fixed tests. [tidusjar] - -- Fully fixed #239. [tidusjar] - -- We wan't updating the DB schema. [tidusjar] - - -## v1.7.3 (2016-05-25) - -### **Fixes** - -- Fixed the release build issue where we could not access the settings #239. [tidusjar] - - -## v1.7.2 (2016-05-25) - -### **Fixes** - -- Fixed a small bug where an exception would get thrown. [tidusjar] - -- Build version bump. [tidusjar] - -- Cleanup. [tidusjar] - -- Typo. [tidusjar] - -- Fixed #241. [tidusjar] - -- Fixed #239. [tidusjar] - -- Fixed #238. [tidusjar] - -- Small UI tweaks/improvements. [Drewster727] - - -## v1.7.1 (2016-05-24) - -### **New Features** - -- Update version. [tidusjar] - -### **Fixes** - -- Fixed an issue with the auth page when running with a reverse proxy. [tidusjar] - - -## v1.7 (2016-05-24) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the ability to get the apikey from the api if you provide a correct username and password. Added more unit tests Added the ability to change a users password using the api refactored the Usermapper and made it unit testsable. [tidusjar] - -- Update. [tidusjar] - -- Added in an audit table. Since we are now allowing multiple users to change and modify things we need to audit this. [TidusJar] - -- Added the updater to the soloution and did a bit of starting code. [TidusJar] - -- Updated the claims so we can support more users. Added a user management section (not yet complete) Added the api to the solution and a api key in the settings (currently only gets the requests). [TidusJar] - -- Updated packages. [TidusJar] - -- Added a retry handler into the solution. We can now retry failed api requests. [TidusJar] - -- Update README.md. [Jamie] - -- Added Released propety to RequestViewModel. Added Released filter to the Requests page. [Chris Lees] - -- Added #27 to albums. [tidusjar] - -- Added the actual notification part of #27. [tidusjar] - -- Added the missing baseurl bit on the login page for #72. [tidusjar] - -- Added the 'enable user notifications' to the email settings view and model. [tidusjar] - -- Update README.md. [Jamie] - -### **Fixes** - -- Remove pointless test, change the default theme and fix a small bug. [tidusjar] - -- Fixed api. [tidusjar] - -- Finished #26. [tidusjar] - -- Plex theme. [tidusjar] - -- Implimented a theme changer, waiting for the Plex theme. [tidusjar] - -- Finished #222 #205. [tidusjar] - -- Started working on #26. [tidusjar] - -- Undid some small changes that was checked in by accident. [tidusjar] - -- #164 has been resolved. [tidusjar] - -- Resolved #224 , Removed the 'SSL' option from the email notification settings. We will now use the correct secure socket options (SSL/TLS) for your email host. [tidusjar] - -- Small changes. [tidusjar] - -- #27 fully finished. [tidusjar] - -- Fixed #215. [tidusjar] - -- Using Mailkit to fix #204. [tidusjar] - -- Color. [tidusjar] - -- Fully finished #27 just need to test it! [tidusjar] - -- Fixed test. [tidusjar] - -- Styling for #27. [tidusjar] - -- I think the auto updater is finished! #29. [tidusjar] - -- I think we have finished the main bulk of the auto updater #29. [tidusjar] - -- #222 #205 more ! Started getting the settings out. [tidusjar] - -- Removed the service locator from the base classes and added in some Api tests added all the tests back in! [tidusjar] - -- More work on the api and documentation #222 #205. [tidusjar] - -- Started documenting the API we now have swagger under ~/apidocs #222 #205. [tidusjar] - -- Api work for #205 Refactored how we check if the user has a valid api key Added POST request, PUT and DELTE. [tidusjar] - -- First pass of the updater working. #29. [tidusjar] - -- Removed SIGHUP from the termination list #220. [tidusjar] - -- Fixed. [tidusjar] - -- Missing. [tidusjar] - -- Missed out a file. [TidusJar] - -- And some more... [TidusJar] - -- Missed some files. [TidusJar] - -- A bit more work on switching to using user claims so we can support multiple users. [TidusJar] - -- Made the store backup clean up some of the older backups (> 7 days). [TidusJar] - -- More work on the user management. [TidusJar] - -- - Notifications will no longer be send to the admins if they request something. - Looks like we missed out adding the notifications to Music requests, so I added that in. [TidusJar] - -- - Improved the RetryHandler. - Made the tester buttons on the settings pages a bit more robust and added an indication when it's testing (spinner) [TidusJar] - -- Packages. [TidusJar] - -- Nm, [TidusJar] - -- Downgraded packages. [TidusJar] - -- Better handling for #202. [TidusJar] - -- Finished #208 and #202. [TidusJar] - -- This should help #202. [TidusJar] - -- Resolved #209. [TidusJar] - -- Finished #209. [TidusJar] - -- Slight adjustments to #189. [tidusjar] - -- - Added a visual indication on the UI to tell the admin there is a update available. - We are now also recording the last scheduled run in the database. [tidusjar] - -- Did the login bit on #185. [tidusjar] - -- Finished #186. [tidusjar] - -- Fixed #185. [tidusjar] - -- Fixed issue in #27 with albums. [tidusjar] - -- #27 added TV Search to the notification. [tidusjar] - -- Fixed bug. [tidusjar] - -- More work on #27 Added a new notify button to the search UI (Needs styling). Also fixed a bug where if the user could only see their own requests, if they search for something that has been requested, it will show as requested. [tidusjar] - -- Improved the startup of the application. We now properaly parse any args passed into the console. [tidusjar] - -- Additional cacher error handling + don't bother checking the requests when we don't get data back from plex. [Drewster727] - -- Remove old migration code and added new migration code. [tidusjar] - -- Stop the Cachers from bombing out when the response from the 3rd party api returns an exception or invalid response. #171. [tidusjar] - -- Increase the scheduler cache timeframe to avoid losing cache when the remote api endpoints go offline (due to a reboot or some other reason) -- if they're online, the cache will get refreshed every 10 minutes like normal. [Drewster727] - -- Fix the cacher by adding locking + extra logging in the plex checker + use a const key for scheduler caching time. [Drewster727] - -- Small changes. [tidusjar] - -- Switched out the schedulers, this seems to be a better implimentation to the previous and is easier to add new "jobs" in. [tidusjar] - -- Fixed #168. [tidusjar] - -- Fixed #162. [tidusjar] - -- Fix saving the log level. [Drewster727] - -- Set the max json length (fixes large json response errors) [Drewster727] - - -## v1.6.1 (2016-04-16) - -### **New Features** - -- Update README.md. [Jamie] - -- Added a url base. [tidusjar] - -- Change default logging. [tidusjar] - -- Added logging around SickRage. [tidusjar] - -### **Fixes** - -- Bump up the version number ready for the release. [tidusjar] - -- BaseUrl is finally finished! #72. [tidusjar] - -- #72 Login page done. [tidusjar] - -- More changes for the urlbase #72. [tidusjar] - -- Done the auth, cp, logs and sidebar for #72. [tidusjar] - -- Add an extra check when determining if a tv show is already available (also check if it starts with the show name returned from the tv db) [Drewster727] - -- Cache plex library data regardless of whether we have requests in the database or not. [Drewster727] - -- By default don't use a url base. [tidusjar] - -- Return empty array when obtaining queued IDs in sickrage cacher. [Drewster727] - -- Fixed a small bug in the SR cacher. [tidusjar] - -- Fixed when we do not have a base. [tidusjar] - -- More changes for #72. [tidusjar] - -- Fixed exception and all areas will now use the base url #72. [tidusjar] - -- Removed the test code from #72. [tidusjar] - -- Commented out the unit tests as they need to be reworked now. [tidusjar] - -- Finally fixed #72. [tidusjar] - -- Remove test code from plex api GetLibrary method. [Drewster727] - -- Finished up the caching TODO's. [tidusjar] - -- Kick off the schedulers once the web app has started (fixes api errors on start) [Drewster727] - -- Converted the UI back down to .NET 4.5.2. [tidusjar] - -- Fixed #154. [tidusjar] - -- Revert everything (except PlexRequests.UI) back to .NET 4.5.2 -- fixes incompatibilities with the latest version of mono (4.2.3.4) -- fixes notifications not working #152 #147 #141. [Drewster727] - -- #150 start caching plex media as well. refactored the availability checker. NEEDS TESTING. also, we need to make the Requests hit the plex api directly rather than hitting the cache as it does now. [Drewster727] - -- #150 split out the cache subscriptions to make sure they subscribe properly. [Drewster727] - -- #150 sonarr/sickrage cache checking. sickrage has a couple small items left. [Drewster727] - -- Fixed args. [tidusjar] - -- Fixed. [tidusjar] - -- Made the base better. [tidusjar] - -- Remove couchpotato api test code. [Drewster727] - -- Start the initial couchpotato cache call on a separate thread to keep the startup process quick. [Drewster727] - -- Add csproj with file changes from previous commit. [Drewster727] - -- Cache the couchpotato wanted list, update it on an interval, and use it to determine if a movie has been queued already. [Drewster727] - -- I think i've fixed an issue where SickRage reports Show not found. [tidusjar] - -- Set the default log level to info. #141. [tidusjar] - -- #125 refactor async task logic to work with mono. [Drewster727] - -- Fix search spinner sticking around after clearing search text + make the "Requested" and "Available" indicators in the search page different colors. [Drewster727] - -- #125 start indicating in the results if an item is already requested or available. [Drewster727] - -- #145 firefox css dsplay issue. [Drewster727] - -- Fixes for sonarr, we now display the error messages back to the user. [tidusjar] - -- Fixed #144. [tidusjar] - - -## v1.6.0 (2016-04-06) - -### **New Features** - -- Changed the build number. [tidusjar] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Changed the title to a contains but the artist still must match, [tidusjar] - -- Added unit tests to cover the new changes to the availability checker. [tidusjar] - -- Added the music check in the Plex Checker. [tidusjar] - -- Changed around the startup so we cache the profiles after the DB has been created. [tidusjar] - -- Updated where we update the request blobs schema change. [tidusjar] - -- Update SearchModule.cs. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Change the new columns type. [tidusjar] - -- Added a DBSchema so we have an easier way to update the DB. [tidusjar] - -- Added an issue template. [tidusjar] - -- Update README.md. [Jamie] - -- Added back the username into the Session when the admin logs in. This means they do not have to log in twice. [tidusjar] - -- Added happy path tests for the Checker. [tidusjar] - -- Added music to the search and requests page. [tidusjar] - -- Added a scroll to the top thingy and a bit more work on headphones. [tidusjar] - -- Added some tests and fixed the issue where the DB would get created in the wrong place depending on how you launched the application. [tidusjar] - -- Added the settings page for #32. [tidusjar] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Some final tweaks for #32. [tidusjar] - -- Fixed a bug where if we are the admin we didn't add the request to the db. [tidusjar] - -- Fixed an issue where we would add the Sickrage series but it would fail on adding the seasons. [tidusjar] - -- Properly account for future/past dates when humanizing with moment. [Drewster727] - -- Properly display release date on requests page. [Drewster727] - -- Add missing reference for release mode. [Drewster727] - -- #139 remove dependency and usage of humanize() - should help with cross-platform issues. start using moment.js. [Drewster727] - -- Fix selectors for music list on request page to get sorting working. [Drewster727] - -- Fixed the error #32. [tidusjar] - -- Fixed the logs page. [tidusjar] - -- Another attempt at filtering #32. [tidusjar] - -- A bit more error handling #32. [tidusjar] - -- Improved the availabilty check to include music results #32. [tidusjar] - -- Small changes for #32. [tidusjar] - -- A bit more logging for #32. [tidusjar] - -- More headphones #32 I am starting to hate headphones... Sometimes the artists and albums just randomly fail. [tidusjar] - -- #134 temporary workaround for this. [Drewster727] - -- Task.run for startup caching + fix admin module unit test failures. [Drewster727] - -- Cache injection, error handling and logging on startup, etc. [Drewster727] - -- Tweaks for #32. [tidusjar] - -- #132 auto-approve for admins. [Drewster727] - -- Finished the bulk work for Headphones. Needs testing #32. [tidusjar] - -- Made the album search 10x faster. We are now loading the images in a seperate call. #32. [tidusjar] - -- Add a reference to API Interfaces to fix the build. [tidusjar] - -- #114 start caching quality profiles. Set the cache on startup and when obtaining quality profiles in settings. [Drewster727] - -- Work for #32. [tidusjar] - -- #114 first pass at choosing quality profile when approving + focus search input by default and when switching tabs. [Drewster727] - -- #131 fix for default selected tab. [Drewster727] - -- Remove references to obsolete RequestedBy property + start setting the db schema to the app version, and check that in the future for migrations. [Drewster727] - -- Fixed async issue. [Shannon Barrett] - -- Updating SickRage api to verify Season List is up to date. [Shannon Barrett] - -- Work on showing the requests for #32. [tidusjar] - -- Got the search finished up for #32. [tidusjar] - -- Remove test/temp code in UserLoginModule. [Drewster727] - -- A bit more work on #32 started working on requesting it. The DB is a bit of an issue... [tidusjar] - -- Most of the UI work done for #32. [tidusjar] - -- Basic search working for #32. [tidusjar] - -- Mono datetime offset workaround. [Drewster727] - -- #122 store utc time in the databse + obtain timezone offset of the client upon login + offset times returned to client based on session offset. [Drewster727] - -- Method reference bug fix. [Drewster727] - -- Fix search focus z-index issue (hid suggestions options) [Drewster727] - -- Minor search UI adjustments. [Drewster727] - -- #55 first attempt at "suggestions" starting with "Comming Soon" and "In Theaters" [Drewster727] - -- #106 rename sorting options and polish the dropdown UI a bit. [Drewster727] - -- Started adding the api part for headphones #32. [tidusjar] - -- Upped the time of #123. [tidusjar] - -- First attempt at #123. [tidusjar] - -- We now do not show the text Requested By to the user, we also show a 'success' message instead of a warning when something has already been requested. [tidusjar] - -- Show a "no requests yet" message on the requests page (for each cateogory) [Drewster727] - -- Ignore items that are already available when approving in bulk, and simplify the checking + compile css. [Drewster727] - -- Add a better way to merge RequestedBy and RequestedUsers to avoid code duplication and simplify checks. [Drewster727] - -- Don't query the session as much in the modules, rely on a variable from the base class and store the username as needed. [Drewster727] - -- Show the requested by user from legacy request models. [Drewster727] - -- Only show requested by users to admins + start maintaining a list of users with each request. [Drewster727] - -- #96 fix up notification test feature. [Drewster727] - -- Fix the request page sort/approve button alignment. [Drewster727] - -- When pulling requests, set each to approved that is already available (so the UI avoids showing the approve option for already available content) [Drewster727] - -- Mono doesn't seem to have Tls1.2. Let's try TLS 1 #119. [tidusjar] - -- Specify a protocol type of TLS12. Looks like CP doesn't seem to like SSL3 (it is quite old now so understandable) #119. [tidusjar] - -- Made #85 better. [tidusjar] - -- Fixed the tests. [tidusjar] - -- Made the feedback from Sonarr better when Sonarr already has the series #85. [tidusjar] - -- An attempt to fix #108. [tidusjar] - -- Add some "no results" feedback to the searching + minor UI improvements. [Drewster727] - -- Fix notification tests. [Drewster727] - -- UI - increase icon size of nav menu (they were too small before) [Drewster727] - -- #96 Finished adding test functionality to notifications. [Drewster727] - -- #96 add the necessary back-end code to produce a test message for all notification types (still have to add the test buttons for pushbullet/pushover) [Drewster727] - -- #96 modify notifications interface/service to accept a non-type specific settings object. [Drewster727] - -- #96 Email notification test button (others to come) [Drewster727] - -- Minor UI adjustments. [Drewster727] - -- #84 provide an option in settings to resttrict users from viewing requests other than their own. [Drewster727] - -- #54 comma separated list of users who don't require approval + fix a couple request messages (include show title) [Drewster727] - -- Clean up the sorting option names. add a way to see which filter/sort is currently applied. [Drewster727] - -- Fix up the animations. seems to be related to the data-bound attribute causing the animtions not to fire on each .mix object. [Drewster727] - -- Move approve buttons to the tab content. [Drewster727] - -- Allow approving all requests by category. [Drewster727] - -- Fix up sorting on the request page. [Drewster727] - -- Add ubuntu/debian instructions. [Drewster727] - -- #86 - display movie/show title + year in request notifications. [Drewster727] - -- Show the movie/show title when requesting. [Drewster727] - - -## v1.5.2 (2016-03-26) - -### **Fixes** - -- Stoped users from spamming the request button. [tidusjar] - -- Fixed the logger no longer writing to the file. [tidusjar] - -- Fixed #97. [tidusjar] - - -## v1.5.1 (2016-03-26) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added logs to the sidebar. I'm an idiot. [tidusjar] - -### **Fixes** - -- Approve tv shows or movies. [Drewster727] - -- Fixed a bug where if you had auto approve it wouldn't notify you. [tidusjar] - - -## v1.5.0 (2016-03-25) - -### **New Features** - -- Updated version number for release. [tidusjar] - -- Updated the logic for handling specific seasons in Sonarr and Sickrage. [Shannon Barrett] - -- Updated the readme and added some icons to the navbar. [tidusjar] - -- Added the ability to sepcify a username in the email notification settings for external MTA's. We have had to add a new option called Email Sender because of this. #78. [tidusjar] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added a notification model to the notifiers. Added the backend work for sending a notification for an issue report #75. [tidusjar] - -- Added a subdir to CP, SickRage, Sonarr and Plex #43. [tidusjar] - -### **Fixes** - -- And again. [tidusjar] - -- Made the check actually work. [tidusjar] - -- Finished up #68 and #62. [tidusjar] - -- Finished styling on the logger for now. #59. [tidusjar] - -- Fixed #69. [tidusjar] - -- Working on getting the Sonarr component to work correctly. [Shannon Barrett] - -- Fixes issue #62. [Shannon Barrett] - -- Refactored the Notification service to how it should have really been done in the first place. [tidusjar] - -- Fixed the build. [tidusjar] - -- Finished #49. [tidusjar] - -- Finished #57. [tidusjar] - -- Small changes around the filtering. [tidusjar] - -- Finished adding pushover support. #44. [tidusjar] - -- Resolved #75. [tidusjar] - -- Include DB changes. [tidusjar] - -- Done most on #59. [tidusjar] - -- Lowercase logs folder, because you know, linux. #59. [tidusjar] - -- Adding the imdb when requesting. [tidusjar] - -- Fixed an issue where the table didn't match the model. [tidusjar] - -- Improved the status page with the suggestion from #29. [tidusjar] - -- Hooked up most of #49 Just the validation messages need to be done. [tidusjar] - -- Fixed #74 and #64. [tidusjar] - -- Resolved #70. [tidusjar] - -- Finished #71. [tidusjar] - -- Got the filter working on both movie and tv #57. [tidusjar] - -- Started #57, currently there is a bug where the TV list won't filter. [tidusjar] - - -## v1.4.1 (2016-03-20) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update AvailabilityUpdateService.cs. [Jamie] - - -## v1.4.0 (2016-03-19) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Updated the build version ready for the next release. [tidusjar] - -- Added the api and settings page for Sickrage. Just need to do the tester and hook it up #40. [tidusjar] - -- Added the option to set a CP quality #38. [tidusjar] - -- Added the code to lookup the old requests and refresh them with new information from TVMaze. [tidusjar] - -- Update StatusCheckerTests.cs. [Jamie] - -- Update README.md. [Jamie] - -- Added TVMaze to the search. #21. [tidusjar] - -- Added migration code and cleaned up the DB. [tidusjar] - -- Updated the way we add requests. [tidusjar] - -- Updated the Dapper.Contrib package, it had a bug where it wasn't returning the correct Id from inserts. [tidusjar] - -### **Fixes** - -- This fixes #36. [tidusjar] - -- Should fix issue #36. [Shannon Barrett] - -- When we do a batch update we need to reset the cache. [tidusjar] - -- Fixed an issue where the default quality on Sickrage wouldn't work. [tidusjar] - -- Wow, that was a lot of work. - So, I have now finished #40. - Fixed a bug where we was not choosing the correct tv series (Because of TVMaze) - Fixed a bug when checking for plex titles - Fixed a bug where the wrong issue would clean on the UI (DB was correct) - Refactored how we send tv shows - And too many small changes to count. [tidusjar] - -- Fixed the new dependancy with the admin class tests. [tidusjar] - -- Back to what it was :( [tidusjar] - -- Another test for #37. [tidusjar] - -- This should fix #37. [Jamie Rees] - -- Catch the missing table exception when they have a new DB. [Jamie Rees] - -- Exploratory test for #37. [Jamie Rees] - -- Fixed #33 we now have SSL options for Sonarr and CP. [Jamie Rees] - -- Removed all the html from the new TVMaze api (for overview). Added tests to cover the html removal. updated Readme to remove TheTVDB. [Jamie Rees] - -- Fixed tests. [Jamie Rees] - -- Almost fully integrated TVMaze #21 and also improved the fix for #31. [Jamie Rees] - -- Should fix #28. [Shannon Barrett] - -- Fixed #16 and #30. [tidusjar] - -- Modified the adding of request to update the model with the added ID. [tidusjar] - -- Switched over to the new service. [tidusjar] - -- Fixed #25. [Jamie Rees] - - -## v1.3.0 (2016-03-17) - -### **New Features** - -- Added pushbullet to the sidebar. [Jamie Rees] - -- Updated build version for the next release. [Jamie Rees] - -- Updated readme link. [tidusjar] - -- Added ignore to static tests. [tidusjar] - -- Added Pushbullet notifications #8. [tidusjar] - -- Added first implimentation of the Notification Service #8 Added tests to cover the notification service. [tidusjar] - -- Added validation to the Email settings, also increased the availability checker from 2 minutes to 5. [tidusjar] - -### **Fixes** - -- Fixed #22. [Jamie Rees] - -- Started on #16, nothing is hooked up yet. [tidusjar] - -- Fixed tests. [tidusjar] - - -## v1.2.1 (2016-03-16) - -### **New Features** - -- Update Program.cs. [Jamie] - -- Update Program.cs. [Jamie] - -- Added back the reference. [tidusjar] - -### **Fixes** - -- Removed the email notification settings from the settings (for release 1.2.1) [Jamie Rees] - -- Fixed. [Jamie Rees] - -- Resolved #10. [tidusjar] - - -## v1.2.0 (2016-03-15) - -### **New Features** - -- Updated. [Jamie Rees] - -- Updated appveyor. [Jamie Rees] - -- Update appveyor.yml. [Jamie] - -- Added latest version code and view. Need to finish the view #11. [tidusjar] - -- Added test button to Plex. That's fixed #9. [tidusjar] - -- Added test sonarr button #9. [tidusjar] - -- Added more tests. [tidusjar] - -- Added a bunch of logging. [tidusjar] - -- Added the application tester for CP #9. [tidusjar] - -- Added settings page for #8. [tidusjar] - -- Added pace.js. [tidusjar] - -### **Fixes** - -- Finished the notes! Resolved #7. [Jamie Rees] - -- #12. [Jamie Rees] - -- #12. [Jamie Rees] - -- Finished the status page #11 and some more work to #12. [Jamie Rees] - -- Resolved #7. [tidusjar] - -- Small changes. [tidusjar] - -- Yeah... [tidusjar] - -- Fixed #5 and also added some tests to the availability checker. [tidusjar] - -- Started added tests. [Jamie Rees] - -- Fixed an issue where the issues text appears larger. [Jamie Rees] - - -## v1.1 (2016-03-13) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Updated readme. [Jamie Rees] - -- Added the support for TV Series integrating with Sonarr. [Jamie Rees] - -- Added the functionality to pass a port through an argument. [tidusjar] - -- Added the code to get the quality profiles from Sonarr Started plugging that into the UI. [Jamie Rees] - -- Added the spinners #3. [tidusjar] - -- Added the functionality for the admin to clear the issues. [tidusjar] - -- Added the issues to the requests page. [tidusjar] - -- Added user logout method and unit tests to cover it. [tidusjar] - -- Added DeniedUsers to the view. [tidusjar] - -- Added the denied user check to the UserLoginModule. added a test case to cover it. [tidusjar] - -- Added a missing reference. [tidusjar] - -- Added first real test. [tidusjar] - -- Update README.md. [Jamie] - -- Added the latest version of nuget. [tidusjar] - -- Added travisyml. [tidusjar] - -- Added logging. [tidusjar] - -- Added missing files. [tidusjar] - -- Update README.md. [Jamie] - -- Added logging (Still WIP) [tidusjar] - -- Added favicon and also structured the HTML correctly. [tidusjar] - -- Updated the packages so everything is now with the correct framework (4.5.2) [tidusjar] - -- Added in deletion of requests. [tidusjar] - -- Added test code. [tidusjar] - -- Added dashboard. [tidusjar] - -- Added couchpotato page. [Jamie Rees] - -- Added readme to the project and updated it. [Jamie Rees] - -- Added helpers. [tidusjar] - -### **Fixes** - -- Bug fix, Couchpotato settings wouldn't show in release due to a Nancy bug. [Jamie Rees] - -- Small changes. [Jamie Rees] - -- First release, build 1.0.0. [Jamie Rees] - -- Removed the request limit since it's not currently being used. [Jamie Rees] - -- REmoved Sickbeared for the first release. [Jamie Rees] - -- Fixed #4 We now can manually set the status of a request. [tidusjar] - -- Made the pass in the port a bit more robust. [tidusjar] - -- Styling, Added the functionality for the Sonarr Profiles on the Admin page #2 resolved. [tidusjar] - -- Fixed a bug in the Login and added a unit test to cover that. Added a button to approve an individual request. Fixed some minor bugs in the request screen. [Jamie Rees] - -- Fixed the 'responsive' issue for the search and requests pages #3. [tidusjar] - -- Styling! #3. [tidusjar] - -- Navbar category now will follow you to various screens #3. [tidusjar] - -- Fixed bugs with the 'other' reporting issue and also the clear issues. [tidusjar] - -- We now are appending the users name to who wrote the comment. Rather than it being unknown. [tidusjar] - -- More work on submitting issues. [tidusjar] - -- More test changes. [tidusjar] - -- More tests to cover the login. [tidusjar] - -- Refactoring. [tidusjar] - -- Implimented the password part and authentication with Plex. [tidusjar] - -- Initial Use authentication is working. Need to do the password bit. [tidusjar] - -- Some error handling and ensure we are an admin to delete requests. [tidusjar] - -- Fixed the issue where the Release build would not show the admin screens! [tidusjar] - -- Fixes. [tidusjar] - -- Removed the DI part of the service. TinyIOC doesn't want to work with FluentScheduler. [tidusjar] - -- First pass at the plex update service. [tidusjar] - -- Small changes. [Jamie Rees] - -- Started to impliment the Plex checker. This will check plex every x minutes to see if there is any new content and then update the avalibility of the requests. [Jamie Rees] - -- Mre work. [Jamie Rees] - -- Few small changes, added plex settings. [Jamie Rees] - -- Making the configuration actually do something. Setting a default configuration if there is no DB. [Jamie Rees] - -- Remove post build. [Jamie Rees] - -- Small changes. [Jamie Rees] - -- MOre work. [Jamie Rees] - -- Fixed the issue when sending movies to CouchPotato. [Jamie Rees] - -- Add appveyor. [tidusjar] - -- Build it on 4.5. [tidusjar] - -- Upgraded .net to 4.6. [tidusjar] - -- Typo2. [tidusjar] - -- Typo. [tidusjar] - -- Another update. [tidusjar] - -- Fixed. [tidusjar] - -- More logging to figure out why the we cannot access the admin module in a release build. [tidusjar] - -- Firstpass integrating with CouchPotato. [tidusjar] - -- Some styling. [tidusjar] - -- Fixed the plex friends. Added some unit tests, moved the plex auth into it's own page. [tidusjar] - -- Fully switched the TV shows over to use the other provider. [Jamie Rees] - -- Renamed folders. [tidusjar] - -- Assembly updates. [tidusjar] - -- Moved the rest of the projects. [tidusjar] - -- Moved UI. [tidusjar] - -- Mass rename. [tidusjar] - -- Quick changes. [tidusjar] - -- Started switching the TV over to the new provider (TheTVDB). Currently TV search is partially broken. It will search but we are not mapping all of the details. [tidusjar] - -- Implimented the new TV show Provider (needed for Sonarr TheTvDB) [tidusjar] - -- Started the user auth. [tidusjar] - -- Some work on the requests page. [tidusjar] - -- Made the 'requested' better and made the remove look nicer. [tidusjar] - -- Cleaned up the program a tiny bit. [tidusjar] - -- Removed additional namespace. [tidusjar] - -- Fixed some db issues and added a preview. [Jamie Rees] - -- More work on the settings. [Jamie Rees] - -- Upgraded Json.Net and Nancy packages. [Jamie Rees] - -- Plex friends api. [Jamie Rees] - -- Enabled trace logs. [tidusjar] - -- Sql syntax issue fixed. [tidusjar] - -- Fixed release build. [tidusjar] - -- Small updates including assembly version. [tidusjar] - -- Work on the requests page mostly done. [tidusjar] - -- Work on the TV request. the `latest` parameter is not being passed into the requestTvshow. [tidusjar] - -- Missing file. [tidusjar] - -- Using the IoC container now. [tidusjar] - -- Some plex work. [Jamie Rees] - -- More work. [Jamie Rees] - -- Removed the setup code out of the startup, since we attemtp to connect to the DB before that. [Jamie Rees] - -- Some more work. Need to stop the form submitting on a request. [tidusjar] - -- Moved everything up a directory. [tidusjar] - -- Lots of work! [tidusjar] - -- Done most of the movie search work. [Jamie Rees] - -- First pass with RequestPlex. [tidusjar] - -- Initial commit. [Jamie] - - diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index e11d6d914..0734262cf 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -19,5 +19,6 @@ namespace Ombi.Api.Plex Task GetAllEpisodes(string authToken, string host, string section, int start, int retCount); Task GetUsers(string authToken); Task GetAccount(string authToken); + Task GetRecentlyAdded(string authToken, string uri, string sectionId); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/Metadata.cs b/src/Ombi.Api.Plex/Models/Metadata.cs index 28bdf002d..dcde62818 100644 --- a/src/Ombi.Api.Plex/Models/Metadata.cs +++ b/src/Ombi.Api.Plex/Models/Metadata.cs @@ -23,8 +23,8 @@ namespace Ombi.Api.Plex.Models public int leafCount { get; set; } public int viewedLeafCount { get; set; } public int childCount { get; set; } - public long addedAt { get; set; } - public int updatedAt { get; set; } + //public long addedAt { get; set; } + //public int updatedAt { get; set; } public Genre[] Genre { get; set; } //public Role[] Role { get; set; } public string primaryExtraKey { get; set; } diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index b5c6b958d..e6c52d1df 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -147,6 +147,15 @@ namespace Ombi.Api.Plex return await Api.Request(request); } + public async Task GetRecentlyAdded(string authToken, string uri, string sectionId) + { + var request = new Request($"library/sections/{sectionId}/recentlyAdded", uri, HttpMethod.Get); + AddHeaders(request, authToken); + AddLimitHeaders(request, 0, 50); + + return await Api.Request(request); + } + /// /// Adds the required headers and also the authorization header /// diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 92ecf8282..e2f667465 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -175,6 +175,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } } } diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index b328e6daf..1e78d226c 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -19,7 +19,7 @@ namespace Ombi.Schedule IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter, IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache, ISettingsService jobsettings, ISickRageSync srSync, IRefreshMetadata refresh, - INewsletterJob newsletter) + INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedSync) { _plexContentSync = plexContentSync; _radarrSync = radarrSync; @@ -33,6 +33,7 @@ namespace Ombi.Schedule _srSync = srSync; _refreshMetadata = refresh; _newsletter = newsletter; + _plexRecentlyAddedSync = recentlyAddedSync; } private readonly IPlexContentSync _plexContentSync; @@ -47,6 +48,7 @@ namespace Ombi.Schedule private readonly ISettingsService _jobSettings; private readonly IRefreshMetadata _refreshMetadata; private readonly INewsletterJob _newsletter; + private readonly IPlexRecentlyAddedSync _plexRecentlyAddedSync; public void Setup() { @@ -55,7 +57,8 @@ namespace Ombi.Schedule RecurringJob.AddOrUpdate(() => _embyContentSync.Start(), JobSettingsHelper.EmbyContent(s)); RecurringJob.AddOrUpdate(() => _sonarrSync.Start(), JobSettingsHelper.Sonarr(s)); RecurringJob.AddOrUpdate(() => _radarrSync.CacheContent(), JobSettingsHelper.Radarr(s)); - RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(), JobSettingsHelper.PlexContent(s)); + RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(false), JobSettingsHelper.PlexContent(s)); + RecurringJob.AddOrUpdate(() => _plexContentSync.CacheContent(true), JobSettingsHelper.PlexRecentlyAdded(s)); RecurringJob.AddOrUpdate(() => _cpCache.Start(), JobSettingsHelper.CouchPotato(s)); RecurringJob.AddOrUpdate(() => _srSync.Start(), JobSettingsHelper.SickRageSync(s)); RecurringJob.AddOrUpdate(() => _refreshMetadata.Start(), JobSettingsHelper.RefreshMetadata(s)); diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs index a9fadae9d..17a8bbb4f 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexContentSync.cs @@ -4,6 +4,6 @@ namespace Ombi.Schedule.Jobs { public interface IPlexContentSync : IBaseJob { - Task CacheContent(); + Task CacheContent(bool recentlyAddedSearch = false); } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs new file mode 100644 index 000000000..6616f29bc --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Plex +{ + public interface IPlexRecentlyAddedSync : IBaseJob + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 3c00a7a29..0292b6b54 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -62,7 +62,7 @@ namespace Ombi.Schedule.Jobs.Plex private IPlexContentRepository Repo { get; } private IPlexEpisodeSync EpisodeSync { get; } - public async Task CacheContent() + public async Task CacheContent(bool recentlyAddedSearch = false) { var plexSettings = await Plex.GetSettingsAsync(); if (!plexSettings.Enable) @@ -78,7 +78,7 @@ namespace Ombi.Schedule.Jobs.Plex Logger.LogInformation("Starting Plex Content Cacher"); try { - await StartTheCache(plexSettings); + await StartTheCache(plexSettings, recentlyAddedSearch); } catch (Exception e) { @@ -89,14 +89,14 @@ namespace Ombi.Schedule.Jobs.Plex BackgroundJob.Enqueue(() => EpisodeSync.Start()); } - private async Task StartTheCache(PlexSettings plexSettings) + private async Task StartTheCache(PlexSettings plexSettings, bool recentlyAddedSearch) { foreach (var servers in plexSettings.Servers ?? new List()) { try { Logger.LogInformation("Starting to cache the content on server {0}", servers.Name); - await ProcessServer(servers); + await ProcessServer(servers, recentlyAddedSearch); } catch (Exception e) { @@ -105,10 +105,10 @@ namespace Ombi.Schedule.Jobs.Plex } } - private async Task ProcessServer(PlexServers servers) + private async Task ProcessServer(PlexServers servers, bool recentlyAddedSearch) { Logger.LogInformation("Getting all content from server {0}", servers.Name); - var allContent = await GetAllContent(servers); + var allContent = await GetAllContent(servers, recentlyAddedSearch); Logger.LogInformation("We found {0} items", allContent.Count); // Let's now process this. @@ -388,8 +388,9 @@ namespace Ombi.Schedule.Jobs.Plex /// If they have not set the settings then we will monitor them all /// /// The plex settings. + /// /// - private async Task> GetAllContent(PlexServers plexSettings) + private async Task> GetAllContent(PlexServers plexSettings, bool recentlyAddedSearch) { var sections = await PlexApi.GetLibrarySections(plexSettings.PlexAuthToken, plexSettings.FullUri); @@ -413,10 +414,23 @@ namespace Ombi.Schedule.Jobs.Plex } } } - var lib = await PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.key); - if (lib != null) + + if (recentlyAddedSearch) { - libs.Add(lib.MediaContainer); + var container = await PlexApi.GetRecentlyAdded(plexSettings.PlexAuthToken, plexSettings.FullUri, + dir.key); + if (container != null) + { + libs.Add(container.MediaContainer); + } + } + else + { + var lib = await PlexApi.GetLibrary(plexSettings.PlexAuthToken, plexSettings.FullUri, dir.key); + if (lib != null) + { + libs.Add(lib.MediaContainer); + } } } } diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs new file mode 100644 index 000000000..dfcb9cac1 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; +using Ombi.Api.Plex; + +namespace Ombi.Schedule.Jobs.Plex +{ + public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync + { + public PlexRecentlyAddedSync(IPlexContentSync contentSync) + { + _sync = contentSync; + } + + private readonly IPlexContentSync _sync; + + public async Task Start() + { + await _sync.CacheContent(true); + } + + private bool _disposed; + protected virtual void Dispose(bool disposing) + { + if (_disposed) + return; + + if (disposing) + { + _sync?.Dispose(); + } + _disposed = true; + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/JobSettings.cs b/src/Ombi.Settings/Settings/Models/JobSettings.cs index a68ceb8bb..bb536a685 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettings.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettings.cs @@ -6,6 +6,7 @@ public string SonarrSync { get; set; } public string RadarrSync { get; set; } public string PlexContentSync { get; set; } + public string PlexRecentlyAddedSync { get; set; } public string CouchPotatoSync { get; set; } public string AutomaticUpdater { get; set; } public string UserImporter { get; set; } diff --git a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs index db4083fcd..c4fcb8ceb 100644 --- a/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs +++ b/src/Ombi.Settings/Settings/Models/JobSettingsHelper.cs @@ -21,7 +21,11 @@ namespace Ombi.Settings.Settings.Models } public static string PlexContent(JobSettings s) { - return Get(s.PlexContentSync, Cron.Hourly(20)); + return Get(s.PlexContentSync, Cron.HourInterval(6)); + } + public static string PlexRecentlyAdded(JobSettings s) + { + return Get(s.PlexRecentlyAddedSync, Cron.Hourly(0)); } public static string CouchPotato(JobSettings s) { @@ -49,7 +53,6 @@ namespace Ombi.Settings.Settings.Models return Get(s.RefreshMetadata, Cron.Daily(3)); } - private static string Get(string settings, string defaultCron) { return settings.HasValue() ? settings : defaultCron; diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 1ce3778ec..d67ebc698 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -128,6 +128,7 @@ export interface IJobSettings { sickRageSync: string; refreshMetadata: string; newsletter: string; + plexRecentlyAddedSync: string; } export interface IIssueSettings extends ISettings { diff --git a/src/Ombi/ClientApp/app/services/job.service.ts b/src/Ombi/ClientApp/app/services/job.service.ts index 79957af97..2fe940316 100644 --- a/src/Ombi/ClientApp/app/services/job.service.ts +++ b/src/Ombi/ClientApp/app/services/job.service.ts @@ -35,6 +35,10 @@ export class JobService extends ServiceHelpers { return this.http.post(`${this.url}plexcontentcacher/`, {headers: this.headers}); } + public runPlexRecentlyAddedCacher(): Observable { + return this.http.post(`${this.url}plexrecentlyadded/`, {headers: this.headers}); + } + public runEmbyCacher(): Observable { return this.http.post(`${this.url}embycontentcacher/`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html index 0d8b85930..72b78a64d 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html @@ -57,6 +57,12 @@ The Plex Sync is required +
+ + + The Plex Sync is required + +
diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts index 5978e7ab2..1fd8237ae 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts @@ -33,6 +33,7 @@ export class JobsComponent implements OnInit { sickRageSync: [x.sickRageSync, Validators.required], refreshMetadata: [x.refreshMetadata, Validators.required], newsletter: [x.newsletter, Validators.required], + plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required] }); }); } diff --git a/src/Ombi/ClientApp/app/settings/plex/plex.component.html b/src/Ombi/ClientApp/app/settings/plex/plex.component.html index 6beaa3fea..6310a9cf1 100644 --- a/src/Ombi/ClientApp/app/settings/plex/plex.component.html +++ b/src/Ombi/ClientApp/app/settings/plex/plex.component.html @@ -171,19 +171,26 @@
-
+
-
-
-
- -
+
+
+
+
+
+
+
+
+ +
+
+
diff --git a/src/Ombi/ClientApp/app/settings/plex/plex.component.ts b/src/Ombi/ClientApp/app/settings/plex/plex.component.ts index 61b57b393..23bd74225 100644 --- a/src/Ombi/ClientApp/app/settings/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/settings/plex/plex.component.ts @@ -121,7 +121,15 @@ export class PlexComponent implements OnInit, OnDestroy { public runCacher(): void { this.jobService.runPlexCacher().subscribe(x => { if(x) { - this.notificationService.success("Triggered the Plex Content Cacher"); + this.notificationService.success("Triggered the Plex Full Sync"); + } + }); + } + + public runRecentlyAddedCacher(): void { + this.jobService.runPlexRecentlyAddedCacher().subscribe(x => { + if(x) { + this.notificationService.success("Triggered the Plex Recently Added Sync"); } }); } diff --git a/src/Ombi/Controllers/JobController.cs b/src/Ombi/Controllers/JobController.cs index 76ca42c41..a89346250 100644 --- a/src/Ombi/Controllers/JobController.cs +++ b/src/Ombi/Controllers/JobController.cs @@ -117,7 +117,18 @@ namespace Ombi.Controllers [HttpPost("plexcontentcacher")] public bool StartPlexContentCacher() { - BackgroundJob.Enqueue(() => _plexContentSync.CacheContent()); + BackgroundJob.Enqueue(() => _plexContentSync.CacheContent(false)); + return true; + } + + /// + /// Runs a smaller version of the content cacher + /// + /// + [HttpPost("plexrecentlyadded")] + public bool StartRecentlyAdded() + { + BackgroundJob.Enqueue(() => _plexContentSync.CacheContent(true)); return true; } diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 9ffa9d81f..f8e58418a 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -473,6 +473,7 @@ namespace Ombi.Controllers j.UserImporter = j.UserImporter.HasValue() ? j.UserImporter : JobSettingsHelper.UserImporter(j); j.SickRageSync = j.SickRageSync.HasValue() ? j.SickRageSync : JobSettingsHelper.SickRageSync(j); j.RefreshMetadata = j.RefreshMetadata.HasValue() ? j.RefreshMetadata : JobSettingsHelper.RefreshMetadata(j); + j.PlexRecentlyAddedSync = j.PlexRecentlyAddedSync.HasValue() ? j.PlexRecentlyAddedSync : JobSettingsHelper.PlexRecentlyAdded(j); return j; } diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 7a94dbb61..7fc0522ed 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -182,12 +182,15 @@ namespace Ombi // Check if it's in the startup args var appConfig = serviceProvider.GetService(); var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl).Result; - if (baseUrl.Value.HasValue()) + if (baseUrl != null) { - settings.BaseUrl = baseUrl.Value; - ombiService.SaveSettings(settings); + if (baseUrl.Value.HasValue()) + { + settings.BaseUrl = baseUrl.Value; + ombiService.SaveSettings(settings); - app.UsePathBase(settings.BaseUrl); + app.UsePathBase(settings.BaseUrl); + } } } From 66687bd177474923118b93914982befd57b23b8f Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 13 Apr 2018 23:20:22 +0100 Subject: [PATCH 024/495] Fixed the bug where the newsletter CRON was not appearing on the job settings page --- src/Ombi/Controllers/SettingsController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index f8e58418a..30469cd57 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -474,6 +474,7 @@ namespace Ombi.Controllers j.SickRageSync = j.SickRageSync.HasValue() ? j.SickRageSync : JobSettingsHelper.SickRageSync(j); j.RefreshMetadata = j.RefreshMetadata.HasValue() ? j.RefreshMetadata : JobSettingsHelper.RefreshMetadata(j); j.PlexRecentlyAddedSync = j.PlexRecentlyAddedSync.HasValue() ? j.PlexRecentlyAddedSync : JobSettingsHelper.PlexRecentlyAdded(j); + j.Newsletter = j.Newsletter.HasValue() ? j.Newsletter : JobSettingsHelper.Newsletter(j); return j; } From 75a631bfcfcf7f67fd14496b6bc6095edc7baf60 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 13 Apr 2018 23:23:58 +0100 Subject: [PATCH 025/495] !wip changelog --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c557ed19..fa6cf0714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,14 +4,46 @@ ### **New Features** +- Added a new Job. Plex Recently Added, this is a slimmed down version of the Plex Sync job, this will just scan the recently added list and not the whole library. I'd reccomend running this very regulary and the full scan not as regular. [Jamie] + +### **Fixes** + +- Fixed the bug where the newsletter CRON was not appearing on the job settings page. [Jamie] + +- Add base url as a startup argument #2153. [Jamie Rees] + +- Fixed a bug with the RefreshMetadata where we would never get TheMovieDBId's if it was missing it. [Jamie] + + +## v3.0.3173 (2018-04-12) + +### **Fixes** + +- Removed some early disposition that seemed to be causing errors in the API. [Jamie] + + +## v3.0.3164 (2018-04-10) + +### **New Features** + - Added the ability to send newsletter out to users that are not in Ombi. [Jamie] - Added the ability to turn off TV or Movies from the newsletter. [Jamie] +- Update about.component.html. [Jamie] + +- Update about.component.html. [Jamie] + - Added random versioning prefix to the translations so the users don't have to clear the cache. [Jamie] - Added more information to the about page. [Jamie] +- Changed let to const to adhere to linting. [Anojh] + +- Update _Layout.cshtml. [goldenpipes] + +- Update _Layout.cshtml. [goldenpipes] + - Changed the TV Request API. We now only require the TvDbId and the seasons and episodes that you want to request. This should make integration regarding TV a lot easier. [Jamie] ### **Fixes** @@ -22,6 +54,8 @@ - Made some improvements to the Sonarr Sync job #2127. [Jamie] +- Turn off Server GC to hopefully help with #2127. [Jamie Rees] + - Fixed #2109. [Jamie] - Fixed #2101. [Jamie] @@ -44,6 +78,12 @@ - Fixed the issue where movies were not appearing in the newsletter for users with Emby #2111. [Jamie] +- The fact that this button has another style really bothers me. [Louis Laureys] + +- Fix discord current user count. [Avi] + +- Fix broken images and new discord invite. [Avi] + ## v3.0.3111 (2018-03-27) From 87b79dfa167e4d02eb2580132bb0a99816363a48 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 13 Apr 2018 23:46:21 +0100 Subject: [PATCH 026/495] !wip fixed lint --- src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts index 1fd8237ae..d0a7a8b83 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts @@ -33,7 +33,7 @@ export class JobsComponent implements OnInit { sickRageSync: [x.sickRageSync, Validators.required], refreshMetadata: [x.refreshMetadata, Validators.required], newsletter: [x.newsletter, Validators.required], - plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required] + plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required], }); }); } From 964377b1959ea122a49cbaec73559db0d6375de2 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 15 Apr 2018 10:43:08 +0200 Subject: [PATCH 027/495] Add web-app-capable for IOS and Android --- src/Ombi/Views/Shared/_Layout.cshtml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Ombi/Views/Shared/_Layout.cshtml b/src/Ombi/Views/Shared/_Layout.cshtml index cafd75357..cb9985bac 100644 --- a/src/Ombi/Views/Shared/_Layout.cshtml +++ b/src/Ombi/Views/Shared/_Layout.cshtml @@ -75,6 +75,8 @@ O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i + + From 487d68729b1b8f5a16431a24a5bb5bc19e8b9f36 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 15 Apr 2018 17:20:22 +0100 Subject: [PATCH 028/495] !cosmetic changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa6cf0714..4ce452bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### **Fixes** +- Add web-app-capable for IOS and Android. [Thomas] + - Fixed the bug where the newsletter CRON was not appearing on the job settings page. [Jamie] - Add base url as a startup argument #2153. [Jamie Rees] From a837868be5e95030c83f2effce5e8a0fe05f704a Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Mon, 16 Apr 2018 14:45:42 +0100 Subject: [PATCH 029/495] !cosmetic changelog --- CHANGELOG.md | 3610 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3610 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ce452bfb..ecb1a790d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -819,3 +819,3613 @@ - Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] +- Fix non-admin rights (#1820) [Rob Gökemeijer] + +- Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] + +- Add the Issue Reporting functionality (#1811) [Jamie] + +- Removed the forum. [tidusjar] + +- #1659 Made the option to ignore notifcations for auto approve. [Jamie] + +- New Crowdin translations (#1806) [Jamie] + +- Fixed a launch issue. [Jamie] + +- Allow users to login without a password. [Jamie] + +- Fixed the emby notifications not being sent. [Jamie] + +- #1802 and other small fixes. [tidusjar] + +- So... This sickrage thing should work now. [tidusjar] + +- Fixed emby connect login issue. [tidusjar] + +- Stop making unnecessary calls to the update service. [Jamie] + +- Fixed a bug where it blocked users with 0 limits. [Jamie] + +- Done #1788. [tidusjar] + +- More logging. [Jamie] + +- Fixed #1738. [Jamie] + +- Fixed build. [Jamie] + +- Fixed the issue where notifications were not sendind unless we restarted #1732. [tidusjar] + +- Fixed an issue with a trailing space in the subdir. [tidusjar] + +- Fixed #1774. [Jamie] + +- #1773. [Jamie] + +- Roll back rxjs (#1778) [bazhip] + +- Fixed build. [Jamie] + +- Fixed #1763. [Jamie] + +- Fix "content length error" on preview gif (#1768) [OoGuru] + +- New preview gif for Ombi V3 README (#1767) [OoGuru] + +- Remove debug code. [tidusjar] + +- Fix #1762. [tidusjar] + +- Fixed the preset themes not loading. [tidusjar] + +- Fixed #1760 and improvements on the auto updater. We may now support windows services... #1460. [Jamie] + +- Fixed #1754. [Jamie] + +- Hide the subject when it's not being used. [Jamie] + +- Error handling #1749. [Jamie] + +- New Crowdin translations (#1741) [Jamie] + +- #1732 #1722 #1711. [Jamie] + +- Fixed an issue with switching the preset themes. [Jamie] + +- Fixed #1743. [Jamie] + +- Fixed #1742. [tidusjar] + +- Fix #1742. [tidusjar] + +- Fixed landing page. [Jamie] + +- Fixed. [Jamie] + +- Translated the Requests page and fixed #1740. [Jamie] + +- Fix crash. [Jamie] + +- Sickrage done. Ish... So i've written all the code by looking at the API. the key there is i've looked at the api. I have not tested anything so expect this to fail. [Jamie] + +- SickRage settings UI. [Jamie] + +- Fixed #1721. [tidusjar] + +- Fixed the preset themes issue. [tidusjar] + +- New Crowdin translations (#1654) [Jamie] + +- Fix build. [Jamie] + +- #1460. [Jamie] + +- Fixed tests. [Jamie] + +- Return css as MIME text/css. [Jamie] + +- More added for the preset themes. [Jamie] + +- Moved around the custom styles. [Jamie] + +- More renames. [Jamie] + +- Renames. [Jamie] + +- Load the first 100 requests. [Jamie] + +- Reduce the memory consumption #1720. [Jamie] + +- Moved the schedules jobs into it's own database, see if it helps with the db locking #1720. [Jamie] + +- Fixed #1712. [tidusjar] + +- Potential fix for #1702. [tidusjar] + +- Fixed #1708. [tidusjar] + +- Fixed #1677. [tidusjar] + +- Fixed build. [tidusjar] + +- Potential fix for the DB locking issue #1720. [tidusjar] + +- #1698. [Jamie] + +- Fixed #1705. [tidusjar] + +- Fixed #1703. [tidusjar] + +- Finished adding preset themes. [Jamie] + +- Fixed #17000. [Jamie] + +- Remove the themes because waiting for a merge from lerams project. [Jamie] + +- Finsihed adding preset themes. [Jamie] + +- Fixed #1677. [Jamie] + +- Temp fix for #1683. [Jamie] + +- Fixed #1685. [Jamie] + +- Lossless Compression of images saves 83 KB (#1676) [Fish2] + +- Fixed the availability checker. [tidusjar] + +- Fixed build. [tidusjar] + +- Push out missing migration. [tidusjar] + +- Potential fix for #1674. [tidusjar] + +- Fixed an issue with the caching. [tidusjar] + +- Fixed telegram #1667. [tidusjar] + +- Fixed #1663. [tidusjar] + +- Should fix #1663. [tidusjar] + +- Stop logged in users going to the login page. [Jamie] + +- Fixed it not updating. Styles should be good now. [Jamie] + +- Re did some of the styling on the movie search page, let me know your thoughts. [Jamie] + +- Fixed #1657. [Jamie] + +- Fixed #1655. [Jamie] + +- Removed authentication resul. [Jamie] + +- New Crowdin translations (#1651) [Jamie] + +- New Crowdin translations (#1648) [Jamie] + +- New Crowdin translations (#1638) [Jamie] + +- Fixed #1644. [Jamie] + +- Moar logs #1643. [tidusjar] + +- Fixed #1640. [tidusjar] + +- Fixed the null ref exception #1460. [tidusjar] + +- Fixed landing page. [TidusJar] + +- Fixed #1641. [TidusJar] + +- Fixed #1641. [TidusJar] + +- New Crowdin translations (#1635) [Jamie] + +- Fixed #1631 and improved translation support Included startup args for the auto updater #1460 Mark TV requests as available #1632. [tidusjar] + +- Remove 32bit. [Jamie] + +- More 32bit support. [Jamie] + +- We now show "Available" for tv shows that is fully available #1602. [tidusjar] + +- Fixed the issue where we have got an episode but not the related series. #1620. [tidusjar] + +- Fixed the dropdown not working on iOS in the settings #1615. [tidusjar] + +- Fixed sonarr not monitoring the latest season #1534. [tidusjar] + +- Fixed the issue with firefox #1544. [tidusjar] + +- Fixed discord #1623. [tidusjar] + +- Add browserstack thanks (#1627) [Matt Jeanes] + +- Fix the exception #1613. [Jamie] + +- Found where we potentially are setting a new poster path, looks like the entity was being modified and being set as Tracked by entity framework, so the next time we called SaveChangesAsync() it would save the new posterpath on the entity. [Jamie] + +- Small modifications. [Jamie] + +- Fixed #1622. [Jamie] + +- Various improvements to webpack/gulp/vscode support (#1617) [Matt Jeanes] + +- Episodes in requests are now in order #1597 (#1614) [masterhuck] + +- Fixed a null reference issue in the Plex Content Cacher. [Jamie.Rees] + +- Fixed #1610. [tidusjar] + +- Really fixed the build this time. [tidusjar] + +- Fixed build. [tidusjar] + +- Made the updater work again #1460. [tidusjar] + +- Adding logging into the auto updater and also added more logging around the create inital user for #1604. [tidusjar] + +- Fixed the issue where we did not check if they are already in sonarr when choosing certain options #1540. [tidusjar] + +- We can now delete tv child requests and the parent will get remove #1603. [tidusjar] + +- Finished the api changes requested #1601. [tidusjar] + +- Fixed the Hangfire server timeout issue #1605. [tidusjar] + +- Fixed notifications not sending #1594. [tidusjar] + +- Fixed #1583 you can now delete users. Fixed the issue where the requested by was not showing. Finally fixed the broken poster paths. [tidusjar] + +- Fixed the issue where movie requests were no longer being requested. [tidusjar] + +- Started adding some more unit tests #1596. [Jamie.Rees] + +- #1588 When we make changes to any requests that we can trigger a notification, always send it to all notification agents, even if the user wont recieve it. [Jamie.Rees] + +- Add a message when email notifications are not setup when requesting a password reset. #1590. [Jamie.Rees] + +- Removed text that we no longer need. [Jamie.Rees] + +- Fixed #1574. [Jamie.Rees] + +- #1460 looks like the permissions issue has been resolved. Just need to make sure the Ombi process is terminated. [Jamie.Rees] + +- Put back the old download code. [Jamie.Rees] + +- Test. [Jamie] + +- Build sln. [Jamie.Rees] + +- Order by the username #1581. [Jamie.Rees] + +- Remove sonarr episodes from the cache table. [Jamie.Rees] + +- Couchpotato finished. [tidusjar] + +- Disable run import button if no import options are selected. [tidusjar] + +- Fixed #1574. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixes the issue with non windows systems unable to unzip the tarball #1460. [tidusjar] + +- Finished the couchpotato settings. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixed #1570 #1571. [tidusjar] + +- Fixed #1547. [tidusjar] + +- Should fix #1538. [tidusjar] + +- Fixed #1553. [tidusjar] + +- Fixed #1546. [tidusjar] + +- Fixed #1543. [tidusjar] + +- Fixes an issue with Movie caching not working on develop branch of Radarr (#1567) [Jeffrey Peters] + +- This adds two fields to the Email Notifications settings page. It allows for the disabling of TLS/SSL as well as the ability to disable certificate validation when sending notification emails. (#1552) [Jeffrey Peters] + +- Fixed typo (#1551) [Codehhh] + +- Use Sqlite storage for Hangfire. [tidusjar] + +- Fixed the overrides #1539 also display it on screen now too. [tidusjar] + +- Fixed #1542 also added VSCode support. [tidusjar] + +- Fixed some cosmetic issues #865. [Jamie.Rees] + +- Fixed #1531. [Jamie.Rees] + +- Small fixes #865. [Jamie.Rees] + +- Some errors fixed and some ui improvements #865. [tidusjar] + +- Auto-scale large images down to container size (#1529) [Avi] + +- Fix logo on login page. (#1528) [Avi] + +- Another potential issue? :/ [tidusjar] + +- Real fix. [tidusjar] + +- #1513 Added storage path. [Jamie.Rees] + +- Fixed the discord issue relating to images #1513. [Jamie.Rees] + +- Fixed the issue sending movies to Radarr #1513 Fixed typo #1524. [Jamie.Rees] + +- Fixed logo on reset password pages fixed the run importer button on the user management settings. [Jamie.Rees] + +- Fixed crash/error #865. [tidusjar] + +- #1513 fixed the landing page and also the reverse proxy images. [tidusjar] + +- #1513 correctly set the child requests as approved. [tidusjar] + +- Fixed an issue that potentially causes as issue when siging into plex #865. [tidusjar] + +- Remove dev branch. [PotatoQuality] + +- Prepare readme for upcoming beta. [PotatoQuality] + +- #1513 partially fixed a bug. [tidusjar] + +- Fixed the exception. [tidusjar] + +- Fixed the application url not saving #1513. [tidusjar] + +- Fixed liniting. [tidusjar] + +- REVERSE PROXY BITCH! #1513. [tidusjar] + +- Fixed a bug where we were marking the wrong episodes as available #1513 #865. [Jamie.Rees] + +- Fixed an issue where we messed up the pages and routing. [Jamie.Rees] + +- Emby user importer is now therer! #1456. [tidusjar] + +- #1513 Added the update available icon. [tidusjar] + +- Fixed the issue of it showing as not requested when we find it in Radarr. Made the tv shows match a bit more to the movie requests Added the ability for plex and emby users to login Improved the welcome email, will only show for users that have not logged in Fixed discord notifications the about screen now checks if there is an update ready #1513. [tidusjar] + +- Support email addresses as usernames #1513. [Jamie.Rees] + +- Link to issue treath. [PotatoQuality] + +- Give correct feedback when testing email notifications #1513. [Jamie.Rees] + +- Report issue removed and the deny dropdown removed #1513. [Jamie.Rees] + +- #1513 removed the discord text when testing pushbullet. [Jamie.Rees] + +- Made a lot of changes around the notifcations to support the custom app name also started on the welcome email ##1456. [Jamie.Rees] + +- Fixed the bug where we were displaying shows where we do not have enough information to request #1513. [Jamie.Rees] + +- #1513 added the network to tv shows. [Jamie.Rees] + +- Fixed the whitespace issue #1513. [Jamie.Rees] + +- Fixed the swagger endpoint #865 #1513 Fixed the custom image issue on the login page Fixed the bug when clicking on the tab on the requests page it would switch to the wrong one Swagger is now back @ /swagger. [tidusjar] + +- Optimized images, Update old compressed image with a new lossless one. (#1514) [camjac251] + +- #1513 #865 Fixed the issue where we do not send the requests to Radarr/Sonarr when approving. [tidusjar] + +- #1506 #865 Fixed an issue with the test buttons not working correctly. [tidusjar] + +- #865 Added donation link. [tidusjar] + +- Fixed a bunch of issues on #1513. [tidusjar] + +- #1460 Added the Updater, it all seems to be working correctly. #865. [Jamie.Rees] + +- Removed percentage. [Jamie.Rees] + +- Fixed linter. [Jamie.Rees] + +- Fixed some bugs in the UI #865. [Jamie.Rees] + +- Improved the search buttons #865. [Jamie.Rees] + +- More logging #865. [Jamie.Rees] + +- Made build faster. [Jamie.Rees] + +- More logging. [Jamie.Rees] + +- Set debug level to Debug for now. [Jamie.Rees] + +- Add linting and indexes for interfaces/services (#1510) [Matt Jeanes] + +- Fixed the issue with the tv search not working #1463. [Jamie.Rees] + +- Latest practices... also probably broke some styles - sorry (#1508) [Matt Jeanes] + +- Build with the branch version. [tidusjar] + +- Build fix. [tidusjar] + +- Fixed build. [tidusjar] + +- Omgwtf so many changes. #865. [tidusjar] + +- Tests. [Jamie.Rees] + +- #1456 Started on the User Importer Also added the remember me button. [Jamie.Rees] + +- Made some UI changes, reworked the Emby and Plex screens to make them more user friendly and no so fugly. #865 Also made the login page placeholder text slightly lighter. [Jamie.Rees] + +- Cake skip verification build stuff #865. [Jamie.Rees] + +- Some fixes around the UI and managing requests #865. [tidusjar] + +- #1486. [Jamie.Rees] + +- #1486. [Jamie.Rees] + +- Upgraded to .net core 2.0 #1486. [Jamie.Rees] + +- #865 Finished the landing page, we now check the server's status. [Jamie.Rees] + +- Fixed build. [TidusJar] + +- Removed the telegram api. [Jamie.Rees] + +- Small changes on the updater #1460 #865. [Jamie.Rees] + +- Remove unused functions. [Dhruv Bhavsar] + +- Make Episode picker similar to Requests Child view. #1457 #1463. [Dhruv Bhavsar] + +- Fix merge conflict for TvRequests component. [Dhruv Bhavsar] + +- Upstream Changes... [Dhruv Bhavsar] + +- Clean up Requests page code by moving children request to old component, remove additional REST calls when merging and update component names to make more sense. [Dhruv Bhavsar] + +- Lots of different UI enhancements and fixes #865. [tidusjar] + +- Gitchangelog. [tidusjar] + +- Fixed the issue where we were using the wrong availability options. [tidusjar] + +- Fixed a bunch of bugs in Ombi #865. [tidusjar] + +- Build versioning. [Jamie.Rees] + +- #1460 The assembly versioning seems to work correctly now. [Jamie.Rees] + +- More build versioning changes #865. [tidusjar] + +- Fixed cake script. [Jamie.Rees] + +- WIP on the build versioning for the Updater #1460 #865. [Jamie.Rees] + +- Versioning. [Jamie.Rees] + +- Package versions. [Jamie.Rees] + +- #1460 #865 working on the auto updater. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Small changes around the roles #865. [tidusjar] + +- Improvements to the UI and also finished the availability checker #865 #1464. [Jamie.Rees] + +- Availability Checker #1464 #865. [Jamie.Rees] + +- Fixed ##1492 and finished the episode searcher for #1464. [Jamie.Rees] + +- #1464. [tidusjar] + +- Reload the settings #1464 #865. [Jamie.Rees] + +- #1464 added the Plex episode cacher #865. [Jamie.Rees] + +- Fixed some issues around the tv requests area Added mattermost and telegram notifications #1459 #865 #1457. [tidusjar] + +- Fix global.json. [Dhruv Bhavsar] + +- Working UI for Requests. Approval/Deny does not work as it doesn't in your code either. [Dhruv Bhavsar] + +- Enable diagnostic on build #865. [Jamie.Rees] + +- Fixed the user token issue #865. [Jamie.Rees] + +- Some small refresh token work #865. [Jamie.Rees] + +- Initial TV Requests UI rebuild. [Dhruv Bhavsar] + +- Made a start on supporting multiple emby servers, the UI needs rework #865. [Jamie.Rees] + +- #865 #1459 Added the Sender From field for email notifcations. We can now have "Friendly Names" for email notifications. [Jamie.Rees] + +- Redirect to the landing page when enabled #1458 #865. [Jamie.Rees] + +- Removed IdentityServer, it was overkill #865. [Jamie.Rees] + +- Fixed another bug with identity. #865 I'm thinking about removing it. Causing more hassle than it's worth. [tidusjar] + +- #1460 #865. [tidusjar] + +- Delete appveyor_old.yml. [Jamie] + +- Fixed path. [Jamie.Rees] + +- Silent build level. [Jamie.Rees] + +- #1459 Forgot to get the Pushbullet agent to look up the pusbullet templates rather than the Discord ones. Updated the Gitchange log. [Jamie.Rees] + +- Made the placeholder color on the login page a bit lighter #865. [Jamie.Rees] + +- Landing and login page changes #865 #1485. [tidusjar] + +- #1458 #865 More work on landing. [Jamie.Rees] + +- Working on the landing page #1458 #865. [tidusjar] + +- A lot of clean up and added a new Image api #865. [Jamie.Rees] + +- Cleaned up the Logging API slightly #1465 #865. [Jamie.Rees] + +- Fixed the Identity Server discovery bug #1456 #865. [tidusjar] + +- Fixed the issue with the Identity Server running on a different port, we can now use -url #865. [Jamie.Rees] + +- Try again. [TidusJar] + +- Publish ubuntu 16.04. [Jamie.Rees] + +- Chnaged the updater job from Minutely to Hourly. [Jamie.Rees] + +- Some work around the Auto Updater and other small changes #1460 #865. [Jamie.Rees] + +- Missed a file. [tidusjar] + +- Fixed the swagger issue. [tidusjar] + +- RDP issues. [tidusjar] + +- Appveyor build rdp investigation. [tidusjar] + +- Working on the requests page #1457 #865. [tidusjar] + +- Made the password reset email style the same as other email notifications #1456 #865. [Jamie.Rees] + +- Fixed some bugs around the authentication #1456 #865. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Fixed build #1456. [Jamie.Rees] + +- #1456 #865 Started on allowing Plex Users to sign in through the new authentication server. [Jamie.Rees] + +- Removed covalent. [Jamie.Rees] + +- #1456 Reset Password stuff #865. [Jamie.Rees] + +- Finished implimenting Identity with IdentityServer4. #865 #1456. [Jamie.Rees] + +- Moved over to using Identity Server with Asp.Net Core Identity #1456 #865. [Jamie.Rees] + +- Started on the requests rework #865. [Jamie.Rees] + +- Extended the Emby API. [Jamie.Rees] + +- Started reworking the usermanagement page #1456 #865. [tidusjar] + +- Lots of refactoring #865. [Jamie.Rees] + +- Created an individual user api endpoint so we can make the user management pages better #865. [TidusJar] + +- Lot's of refactoring. [Jamie.Rees] + +- #1462 #865 Had to refactor how we use notificaitons. So we now have more notification fields about the request. [Jamie.Rees] + +- Looks like Sonarr is finished and works. A lot simplier this time around. #865. [tidusjar] + +- More work on the Sonarr Api Integration #865. [tidusjar] + +- Started on sonarr #865. [tidusjar] + +- Small changes #865. [tidusjar] + +- Damn son. So many changes... Fixed alot of stuff around tv episodes with the new DB model #865. [tidusjar] + +- Fixed the TV Requests issue #865. [Jamie.Rees] + +- Fixed a load of bugs need to figure out what is wrong with tv requests #865. [tidusjar] + +- #865 rework the backend data. Actually use real models rather than a JSON store. [Jamie.Rees] + +- Fixed the build issue #865. [tidusjar] + +- Allow us to use Emby as a media server. [tidusjar] + +- More Update #865. [Jamie.Rees] + +- Deployment changes. [Jamie.Rees] + +- More work on the Updater. [Jamie.Rees] + +- Lots of fixes. Becoming more stable now. #865. [tidusjar] + +- Small fixes around the searching. [Jamie.Rees] + +- Some rules #865. [Jamie.Rees] + +- Oops. [TidusJar] + +- Started on the Discord API settings page. [TidusJar] + +- Email Notifications are now fully customizable and work! #865. [Jamie.Rees] + +- Small changes and fixed some stylingon the plex page #865. [Jamie.Rees] + +- More on #865 TODO, Find out whats going on with the notifications and why exceptions are being thrown. [Jamie.Rees] + +- Oops. [Jamie.Rees] + +- Ok #865 fixed the published exe. [Jamie.Rees] + +- Fixed errors. [Jamie.Rees] + +- Fixed build script. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Some more #865. [Jamie.Rees] + +- Create appveyor.yml. [Jamie] + +- The Approving child requests now work! [Jamie.Rees] + +- Fixed many bugs #865. [Jamie.Rees] + +- Loads of changes, improved the movie search stylings is back. [Jamie.Rees] + +- Moved to webpack and started on new style. [Jamie.Rees] + +- Fixed the TV search via Trakt not returning Images anymore. #865. [Jamie.Rees] + +- Rules changes and rework. [Jamie.Rees] + +- Request Grid test. [Jamie.Rees] + +- Small cleanup #865. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Started the Radarr Settings #865. [Jamie.Rees] + +- Massive amount of rework on the plex settings page. It's pretty decent now! #865. [tidusjar] + +- Fixed build. [Jamie.Rees] + +- Fixed build. [tidusjar] + +- Rules #865. [tidusjar] + +- Stuff. [Jamie.Rees] + +- Forgot to uncomment. [Jamie.Rees] + +- Tetsd. [Jamie.Rees] + +- Build task changes. [Jamie.Rees] + +- Adsa. [Jamie.Rees] + +- Appveyor. [Jamie.Rees] + +- Stuff around tokens and also builds. [Jamie.Rees] + +- Finished the Plex Content Cacher. Need to do the episodes part but things are now showing as available! #865. [tidusjar] + +- Small user changes #865. [Jamie.Rees] + +- Stuff #865 need to work on the claims correctly. [Jamie.Rees] + +- Reworked the TV model AGAIN #865. [Jamie.Rees] + +- The move! [Jamie.Rees] + +- Fixed build #865. [Jamie.Rees] + +- Fixed the user management #865. [Jamie.Rees] + +- #865 Added support for multiple plex servers. [Jamie.Rees] + +- Bleh. [tidusjar] + +- Small changes. [Jamie.Rees] + +- Fixed the build. [Jamie.Rees] + +- Fixes. [Jamie.Rees] + +- Bundling changes. [Jamie.Rees] + +- Some series information stuff, changes the pace theme too. [Jamie.Rees] + +- Docker support and more, redesign the episodes. [tidusjar] + +- Stuff around episode/season searching/requesting. [Jamie.Rees] + +- Removed redundant folders. [tidusjar] + +- Lots of backend work. [tidusjar] + +- Fixed build. [Jamie.Rees] + +- TV Request stuff. [Jamie.Rees] + +- Work around the user management. [tidusjar] + +- More. [Jamie.Rees] + +- Lots and Lots of work. [Jamie.Rees] + +- Diagnostic changes. [tidusjar] + +- Fixed hangfire exception. [tidusjar] + +- Remove xunit. [tidusjar] + +- Lots more work :( [Jamie.Rees] + +- More changes. [tidusjar] + +- #865. [Jamie.Rees] + +- Small changes. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- More mapping. [Jamie.Rees] + +- Mapping mainly. [Jamie.Rees] + +- Fix systemjs config not being included. [Matt Jeanes] + +- Fixed bundling and various improvements. [Matt Jeanes] + +- Finished the emby wizard #865. [tidusjar] + +- Finished the wizard #865 (For Plex Anyway) [tidusjar] + +- Small changes. [tidusjar] + +- More work on Wizard and Plex API #865. [tidusjar] + +- Settings. [Jamie.Rees] + +- Settings for Ombi. [Jamie.Rees] + +- Fixed some issues around the identity. [Jamie.Rees] + +- #865 more for the authentication. [tidusjar] + +- Auth. [Jamie.Rees] + +- More on the search and requests page. It's almost there for movies. Need to add some filtering logic #865. [tidusjar] + +- #865. [Jamie.Rees] + +- Fixed build. [tidusjar] + +- Messing around with the settings. [tidusjar] + +- Fixed the yml. [Jamie.Rees] + +- Remove unneeded bundle config. [Matt Jeanes] + +- Redo dotnet publish targets. [Jamie.Rees] + +- Bundling changes. [Jamie.Rees] + +- Stuff. [Jamie.Rees] + +- Move app into wwwroot. [Jamie.Rees] + +- Put uglify back in! [Jamie.Rees] + +- Wrong line. [Jamie.Rees] + +- Matt is helping. [Jamie.Rees] + +- Revert. [tidusjar] + +- Small tweaks. [tidusjar] + +- Upgrade to .Net Standard 1.6. [tidusjar] + + +## v2.2.1 (2017-04-09) + +### **New Features** + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added the forums. [tidusjar] + +- Updates. [tidusjar] + +- Update gulpfile.js. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update gulpfile.js. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added a retry policy around the emby newsletter. [Jamie.Rees] + +### **Fixes** + +- Revert "Merge branch 'DotNetCore' into dev" [tidusjar] + +- More borken build. [Jamie.Rees] + +- Started adding requesting. [Jamie.Rees] + +- Done the movie searching. [tidusjar] + +- #865. [tidusjar] + +- More. [tidusjar] + +- Moar. [tidusjar] + +- Small changes. [tidusjar] + +- Styling. [Jamie.Rees] + +- MOre changes. [Jamie.Rees] + +- Spacing. [Jamie.Rees] + +- Try again. [Jamie.Rees] + +- More. [Jamie.Rees] + +- Again. [Jamie.Rees] + +- Anbother. [Jamie.Rees] + +- Another. [Jamie.Rees] + +- Another. [Jamie.Rees] + +- Retry. [Jamie.Rees] + +- A. [Jamie.Rees] + +- Fixed. [Jamie.Rees] + +- Cahnge 2. [Jamie.Rees] + +- Appveyor change. [Jamie.Rees] + +- The start of a new world. [Jamie.Rees] + +- Fixed the migration number and order by the added date for the newsletter #1264. [tidusjar] + +- Forgot this change. [tidusjar] + +- Also fixed the issue for the Emby Newsletter where episodes were not getting added :( [tidusjar] + +- #1264 "They may take our lives, but they'll never take our freedom!" [tidusjar] + +- Finished reworking the Sonarr Integration. Seems to be working as expected, faster and most stable. It's Not A Toomah! [tidusjar] + +- Small bit of work. [Jamie.Rees] + +- Made a start on the new Sonarr integration. [tidusjar] + +- For test emails, if there is no new content then just grab some old data. [tidusjar] + +- Fixed an issue where the emby newsletter was always showing series. [tidusjar] + + +## v2.2.0 (2017-03-30) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added a new setting for the Netflix option, we can now disable it appearing in the search. [tidusjar] + +- Update German Translation. [Marius Schiffer] + +- Added a release notes page, you can access via Admin>Updates>Recent Changes tab. Note to self, need to put better comments in for users to understand! [Jamie.Rees] + +- Added gravitar image. [Jamie.Rees] + +- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] + +- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] + +- Added some logging around API calls. [smcpeck] + +- Changed IEmbyAvailabilityChecker to use IEnumberables + checking actor search against Emby content + PR feedback. [smcpeck] + +- Changed actor searching to support non-actors too. [smcpeck] + +- Added a 10 second timer to refresh some new caching I put in. [smcpeck] + +- Added root folder and approving quality profiles in radarr #1065. [tidusjar] + +- Added some debugging code around the newsletter for Emby #1116. [tidusjar] + +- Added a TMDB Rate limiter for the newsletter. [tidusjar] + +- Added port check in wizard. also fixed favicon. [tidusjar] + +- Update Radarr placeholder. [d2dyno] + +- Added the user login for emby users #435. [tidusjar] + +- Added User Management support for Emby #435. [tidusjar] + +- Added emby to the sidebar #435. [tidusjar] + +- Added API endpoint for /actor/new/ to support searching for movies not already available/requested. [smcpeck] + +- Update ISSUE_TEMPLATE.md. [Jamie] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +### **Fixes** + +- Translation changes. [Jamie.Rees] + +- Syntax error. [tidusjar] + +- Fixed an issue where we were retrying the API call when the Plex users login creds were invalid. #1217. [tidusjar] + +- Slightly increased the wait time for the emby newsletter also fixed a potential error in the plex user checker. [Jamie.Rees] + +- Fixed an issue where we were not notifiying emby users. [Jamie.Rees] + +- Fixed the issue where the recent changes page was not showing the correct date. #1296. [Jamie.Rees] + +- Fixed #1252 (Show the correct user type on the management page for Plex Users) [Jamie.Rees] + +- Fixed the casting error #1292. [Jamie.Rees] + +- Fix test newletter not sending when empty. [Dhruv Bhavsar] + +- Quick fix for email false positive message. ISSUE: #1286. [Dhruv Bhavsar] + +- Fixes around the newsletter. We will now correctly show newly added shows and also newly added episodes. #1163. [tidusjar] + +- Fixed a sonarr deseralization error. [tidusjar] + +- Increased the delay for the Episode information api calls. #1163. [tidusjar] + +- Looks like we were overloading emby with out api calls. [tidusjar] + +- Fixed the root path escaping issue for Radarr too! [tidusjar] + +- Some small backend newsletter changes, we can now detect if there are any movies and/or tv shows, if there are none then we will no longer send out an empty newsletter. [Jamie.Rees] + +- Remoddeled the notificaiton settings to make it easier to add more. This is some techinical changes that no one except me will ever notice :( [Jamie.Rees] + +- Fixed #1234. [Jamie.Rees] + +- A fix to the about page and also started to rework the notification backend slightly to easily add more notifications. [Jamie.Rees] + +- Adding more logging into the Plex Cacher. [Jamie.Rees] + +- #1218 changed the text when we cannot display release notes for dev and EAP branches. [Jamie.Rees] + +- Fix for #1236. [SuperPotatoMen] + +- Tooltips. [Jamie.Rees] + +- #236. [Jamie.Rees] + +- #1102. [Jamie.Rees] + +- Done #1012. [Jamie.Rees] + +- Oops #1134. [Jamie.Rees] + +- Fixed #1121. [Jamie.Rees] + +- Fixed #1210. [Jamie.Rees] + +- Fixed typo #1134. [Jamie.Rees] + +- Fixed #1223. [Jamie.Rees] + +- Another newsletter fix attempt #1163 #1116. [tidusjar] + +- Fixup! Reset the branch on v2.1.0 tag to get to a shared state between dev and Master. [distaula] + +- Fixed a bug in the Plex Newsletter. [tidusjar] + +- Typo. [tidusjar] + +- Fixed around the newsletter and a small feature around the permissions/features (#1215) [Jamie] + +- Fixed #1189. [tidusjar] + +- Fixed #1195. [Jamie.Rees] + +- Fixed #1195. [Jamie.Rees] + +- Fixed #1192. [Jamie.Rees] + +- Fixed issue where we could get null rating keys on Plex. [tidusjar] + +- Needed to treat a 201 as success, too. + removed some commented out code. [Shaun McPeck] + +- Normalized spacing/tabs. [smcpeck] + +- Move local user login to be the first thing checked; renamed old Api variable to PlexApi now that Emby is in play. [smcpeck] + +- Remove all the polling/retry logic around HP requests. This was a problem do to not properly awaiting the initial AddArtist API call being sent to HP. Also fix SetAlbumStatus to use ReleaseId instead of MusicBrainsId (same fix previously applied to AddArtist). [smcpeck] + +- Restore checking of HTTP StatusCode on ApiRequests; remove checking of response.ErrorException. [smcpeck] + +- Reverted (for now) non-200 response handling; added some extra logging. [smcpeck] + +- Tweaked ApiRequest behavior on non-200 responses; think it was breaking login. :-" [smcpeck] + +- Only deserialize response payload in ApiRequest when StatusCode == 200. Will a default return value in other cases cause other issues? [smcpeck] + +- Headphones - added releaseID to generic RequestedModel and passing that through to HP request. Their API doesn't request via the MusicBrainzId. [smcpeck] + +- Fixed #1038. [tidusjar] + +- Fixed a slight issue where we could click the change folders button rather than the dropdown arrow #1189. [tidusjar] + +- Bunch of updater files. [tidusjar] + +- #1163 #117. [tidusjar] + +- Removed some unnecessary 'ConfigureAwait` uses. [smcpeck] + +- Remove meaningless html class from actor searching checkbox. [smcpeck] + +- Fixed an issue where we were not always showing movies from external programs. [tidusjar] + +- Remove extra delay when filtering out existing movies. [smcpeck] + +- Post merge build fixes. [smcpeck] + +- Fix. [tidusjar] + +- Fixed #1177. [tidusjar] + +- Fixed #1152. [tidusjar] + +- Fixed #1123. [tidusjar] + +- Fixed a bug when sending to radarr. [tidusjar] + +- Fixed #1133. [tidusjar] + +- Fixed issues img. [Jamie.Rees] + +- Stop Plex being enabled on the first time installing #1048. [Jamie.Rees] + +- The landing page now works for emby #435. [tidusjar] + +- Fixed #1104. [tidusjar] + +- Fixed #1090. [tidusjar] + +- Fixed #1103. [tidusjar] + +- Small changes. [tidusjar] + +- Break out Mass Email feature into its own tab, upgrade Font Awesome and clean up some comments. [dhruvb14] + +- Fix typo. [Travis Bybee] + +- Fixed #1066. [Jamie.Rees] + +- Fixed broken builds. [Jamie.Rees] + +- Fixed #1083. [Jamie.Rees] + +- #1049. [tidusjar] + +- Fixed #1071. [tidusjar] + +- Fixed #1048 #1081. [tidusjar] + +- #1074. [Jamie.Rees] + +- #1069. [Jamie.Rees] + +- Fix for #1068. [tidusjar] + +- Remove duplciate tv show status. [tidusjar] + +- Some request ui changes. [tidusjar] + +- Removed references to Plex. [tidusjar] + +- Removed plex from the scheduled jobs ui. [tidusjar] + +- First run of the newsletter set it to a test. [tidusjar] + +- Reworked the newsletter for Emby! Need to rework it for Plex and use the new way to do it. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixed the mass email, it was only being set to users with the newsletter feature #358. [tidusjar] + +- Removed Plex Request from the notifications. [tidusjar] + +- Finish implementing mass email feature. [dhruvb14] + +- @tidusjar pointed out runtime error!! [dhruvb14] + +- Does not compile, need to get data from UI into nancy somehow and figure out why IMassEmail is not initializing. [dhruvb14] + +- Begin Implementing Mass Email Section. [dhruvb14] + +- Hide the auto update btn #236 Fixed where we were not populating the emby episodes #435. [tidusjar] + +- Fix Radarr labels. [d2dyno] + +- Fixed pace loader. [Jamie.Rees] + +- Check if Emby/Plex is enabled before starting the job. [Jamie.Rees] + +- Fixed #1036. [Jamie.Rees] + +- Fixed a typo and changed wording. [Torkil Liseth] + +- Fixed #1035. [Jamie.Rees] + +- Fix for #1026. [Jamie.Rees] + +- Fixed #1042. [tidusjar] + +- #435. [Jamie.Rees] + +- #435 Started the wizard. [Jamie.Rees] + +- Removed. [tidusjar] + +- Final Fixes. [dhruvb14] + +- Partial fix for broken HR tag's in Email... [dhruvb14] + +- DAMN! #435 that's a lot of code! [tidusjar] + +- Started adding Emby, Lots of backend work done. Need a few more services done and login and user management. #435. [tidusjar] + +- UI changes to add checkbox and support searching for only new matches via new API. [smcpeck] + +- REFACTOR: IAvailabilityChecker - changed arrays to IEnumerables. [smcpeck] + +- UI changes to consume actor searching API. [smcpeck] + +- API changes to allow for searching movies by actor. [smcpeck] + +- Enforcing async/await in synchronous methods that were marked async. [smcpeck] + + +## v2.1.0 (2017-01-31) + +### **New Features** + +- Update README.md. [Jamie] + +- Update .gitattributes. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added the new labels to the search. [tidusjar] + +- Added a switch to use the new search or not, just in case people do not like it. added a migration to turn on the new search. [Jamie.Rees] + +- Added a bunch of categories for tv search similar to what we have for movies. [Jamie.Rees] + +### **Fixes** + +- Fixed typos. [Haries Ramdhani] + +- Fix typo in readme. [tdorsey] + +- Fixed #985. [Jamie.Rees] + +- FIxed #978. [tidusjar] + +- Fixed the approval issue for #939. [tidusjar] + +- Some general improvements. [tidusjar] + +- Turned off migration for now. [tidusjar] + +- Fixed #998. [tidusjar] + +- Additional movie information. [Jamie.Rees] + +- Debug info around the notifications. [Jamie.Rees] + +- Small changes. [tidusjar] + +- Fixed #995. [tidusjar] + +- Fix for #978. [tidusjar] + +- Fixed #991. [tidusjar] + +- Fixed the login issue and pass Radarr the year #990. [Jamie.Rees] + +- More small tweaks around the username/alias. [Jamie.Rees] + +- Possible issue with the empty username. [Jamie.Rees] + +- Potential Fix for #985. [Jamie.Rees] + +- Small changed to the sidebar. [Jamie.Rees] + +- Small changes. [Jamie.Rees] + +- Done #627. [Jamie.Rees] + +- Finished #535 #445 #170. [tidusjar] + +- Fixed tests. [Jamie.Rees] + +- Started to add the specify Sonarr root folders. [Jamie.Rees] + +- Fixed #968. [Jamie.Rees] + +- Fixed #970. [Jamie.Rees] + +- Done #924. [Jamie.Rees] + +- Fixed. [Jamie.Rees] + +- Fixed #955. [Jamie.Rees] + +- #956. [Jamie.Rees] + +- Fixed #947. [Jamie.Rees] + +- #951. [tidusjar] + +- Finished #923 !!! [tidusjar] + +- More for #923. [Jamie.Rees] + +- Radarr integartion in progress #923. [tidusjar] + +- Fixed #940 don't show any shows without a tvdb id. [tidusjar] + +- Finished #739. [Jamie.Rees] + +- Initial impliementation of #739. [Jamie.Rees] + +- Improved the search UI and made it more consistant. Finished the Netflix API Part #884. [Jamie.Rees] + + +## v2.0.1 (2017-01-16) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added a netflix api. [Jamie.Rees] + +### **Fixes** + +- Fixed #934. [Jamie.Rees] + + +## v2.0 (2017-01-14) + +### **New Features** + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Update README.md. [SuperPotatoMen] + +- Added the settings for #925 but need to apply the settings to the UI. [Jamie.Rees] + +- Changed the settings name from Plex Requests to Ombi. [Jamie.Rees] + +- Added support for Managed Users #811. [Jamie.Rees] + +- Change solution name in travis. [mhann] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +### **Fixes** + +- Finished #925. [Jamie.Rees] + +- Some TODO's. [Jamie.Rees] + +- Fixed #915. [Jamie.Rees] + +- Fixed the issue where notifications are not being sent to users with Aliases #912. [Jamie.Rees] + +- Fixed #891. [Jamie.Rees] + +- Fix indentation issue. [Marcus Hann] + +- Implement simple button. [Marcus Hann] + +- Plex Username Case Sensitivity Fix. [thegame3202] + +- Fixed #882. [Jamie.Rees] + +- Api changed again, so more fixes for #878. [Jamie.Rees] + +- Possible fix for #893. [Jamie.Rees] + +- Fixed #898. [Jamie.Rees] + +- Fixed #878. [TidusJar] + +- * userManagementController.js: fixed #881. [TidusJar] + +- More work on watcher, should all be good now. #878. [Jamie.Rees] + +- Delete PlexRequests.sln.DotSettings. [Jamie] + +- Fixed #862. [Jamie.Rees] + +- #399 and #398 finished. [Jamie.Rees] + +- More work on #399. [Jamie.Rees] + +- Finished #884. [Jamie.Rees] + +- More for #844. [Jamie.Rees] + +- Another #844. [Jamie.Rees] + +- Fixed a dependancy issue with #844. [Jamie.Rees] + +- Finished the main part of #844 just need testing. [Jamie.Rees] + +- Fixed #832. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Fix tiny readme typo. [mhann] + +- Fixed #850 also started #844 (Wrote the API interaction) [Jamie.Rees] + +- #801 #292 done. [Jamie.Rees] + +- Should fix #841 #835 #810. [Jamie.Rees] + +- Fix small typo in ticket overview page. [mhann] + +- More work on the combined login. [Jamie.Rees] + +- Fixed db issue. [Jamie.Rees] + +- Name changes. [Jamie.Rees] + +- All Sln changes. [tidusjar] + +- Moved API Sln dir. [tidusjar] + +- Fixed build. [tidusjar] + +- Moved namespaces. [tidusjar] + +- Renamed zip. [tidusjar] + +- Product name change. [tidusjar] + + +## v1.10.1 (2016-12-17) + +### **Fixes** + +- #788 fixed! [tidusjar] + +- Fixed #788 and #791. [tidusjar] + +- #399 #398. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Small refactorings. [Jamie.Rees] + +- #782. [Jamie.Rees] + +- #785. [Jamie.Rees] + + +## v1.10.0 (2016-12-15) + +### **New Features** + +- Update README.md. [Jamie] + +- Added optional launch args for the auto updater. [Jamie.Rees] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update _Navbar.cshtml. [Jamie] + +- Update README.md. [Jamie] + +- Update _Navbar.cshtml. [Jamie] + +- Update README.md. [Jamie] + +- Added a new permission to bypass the request limit. [Jamie.Rees] + +- Update Version1100.cs. [SuperPotatoMen] + +- Added logging around the Newsletter #717. [Jamie.Rees] + +- Added missing migration. [tidusjar] + +- Added loading spinner. [Jamie.Rees] + +- Update UI.resx. [SuperPotatoMen] + +- Update Version1100.cs. [Jamie] + +- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] + +### **Fixes** + +- Fixed an issue where the HTML in the newsletter was incorrect. [Jamie.Rees] + +- Fixed #201. [Jamie.Rees] + +- Fixed #720 and added better error handling around the migrations. [Jamie.Rees] + +- Fixed #769. [Jamie.Rees] + +- Write out the actual file version. [Jamie.Rees] + +- Error checking around GA. [TidusJar] + +- Fixed #761. [tidusjar] + +- Fixed #749 Fixed an issue where we were adding the read only permission when creating the admin. [tidusjar] + +- Fixed #757. [tidusjar] + +- Fixes around the server admin #754. [Jamie.Rees] + +- Removed the trace option from the UI, it is only accessible when appending the url with "?developer" #753. [Jamie.Rees] + +- Fixed an issue where the admin could not be updated. [Jamie.Rees] + +- Fixed #745. [Jamie.Rees] + +- Fixed #748. [Jamie.Rees] + +- Workaround for #748. [SuperPotatoMen] + +- Another attempt to fix #717. [tidusjar] + +- Fixed #744. [Jamie.Rees] + +- Tidied up the warnings. [Jamie.Rees] + +- Small bit of analytics. [Jamie.Rees] + +- Removed the whitelist. [Jamie.Rees] + +- Should fix #696 Fixed an issue with the scheduled jobs where it could use a different trigger if the order between the schedules and the triggers were in different positions in the array... Stupid me, ordering both arrays by the name now. [tidusjar] + +- Some better null object handling #731. [Jamie.Rees] + +- Small tweaks. [Jamie.Rees] + +- Fixed #728. [Jamie.Rees] + +- Fixed #727. [Jamie.Rees] + +- Fixed admin redirect issue. [tidusjar] + +- Fixed #718. [tidusjar] + +- Lots of small fixes and tweaks. [Jamie.Rees] + +- Tidied up some of the angular code, split the UI into it's own directives for easier maintainability. [Jamie.Rees] + +- Small tweaks to the Request Page. [Jamie.Rees] + +- Reverted the PR that may have caused #619. [Jamie.Rees] + +- Some small tweaks around #218 Just added the link to the settings and some angular improvements. [Jamie.Rees] + +- Test. [Jamie.Rees] + +- Attempt at fixing #686. [Jamie.Rees] + +- Finished #707. [Jamie.Rees] + +- Fixed #704. [Jamie.Rees] + +- Fixed #705. [Jamie.Rees] + +- Fixed #706. [Jamie.Rees] + +- #547. [Jamie.Rees] + +- Default tabs #304. [Jamie.Rees] + +- Fixed #703. [Jamie.Rees] + +- #233. [Jamie.Rees] + +- Done #678. [Jamie.Rees] + +- Fixed #670. [Jamie.Rees] + +- #456 Update all the requests when we identify that the username changes. [Jamie.Rees] + +- More user management. [Jamie.Rees] + +- #218. [Jamie.Rees] + +- Fixed build. [tidusjar] + +- Implimented the features #218. [tidusjar] + +- Use the user alias everywhere if it is set #218. [tidusjar] + +- Implimented auto approve permissions #218. [tidusjar] + +- A. [tidusjar] + +- Fixed an IOC issue. [tidusjar] + +- Fixed the issue with user management, needed to implement our own authentication provider. [Jamie.Rees] + +- Small changes including #666. [Jamie.Rees] + +- Done #679. [tidusjar] + +- Reduce the retry time. [Jamie.Rees] + +- Remove all references to the claims. [Jamie.Rees] + +- Lots of fixed and stuff. [Jamie.Rees] + +- Fixed potential crash #683. [Jamie.Rees] + +- Fixed #681. [Jamie.Rees] + +- More user management. [Jamie.Rees] + +- Finishing off the user management page #218 #359 #195. [Jamie.Rees] + +- Finished #646 and fixed #664. [Jamie.Rees] + +- Started on #646. Fixed #657. [Jamie.Rees] + +- Fixed #665. [Jamie.Rees] + +- Migrate users. [TidusJar] + +- Fixed build. [Jamie.Rees] + +- Convert the for to foreach for better readability. Still need to rework this area. [Jamie.Rees] + +- Final Tweaks #483. [Jamie.Rees] + +- Finished #483. [Jamie.Rees] + +- Finished the queue #483. [Jamie.Rees] + +- Started on the queue for requests #483 TV Requests with missing information has been completed. [Jamie.Rees] + +- Fixed build. [Jamie.Rees] + +- Finished the notification for the fault queue. [Jamie.Rees] + +- Finished #556. [Jamie.Rees] + +- Finished #633 (First part of the queuing) [Jamie.Rees] + +- Finished #659 #236 has been modified slightly. Needs testing on Different systems. [Jamie.Rees] + +- Almost finished #659. [Jamie.Rees] + +- Started on #483. [Jamie.Rees] + +- #544. [Jamie.Rees] + +- Fixed #656 and more work on #218. [Jamie.Rees] + +- Fixed some issues with the user management work. [TidusJar] + +- Fixed build issue. [TidusJar] + +- User perms. [Jamie.Rees] + + +## v1.9.7 (2016-11-02) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Potential fix for #629. [TidusJar] + +- Fixed an issue to stop blatting over the base url. [tidusjar] + +- Fixed #643. [TidusJar] + +- Fixed #622. [TidusJar] + + +## v1.9.6 (2016-10-28) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Fixed #586. [Jamie.Rees] + +- Fixed #622. [Jamie.Rees] + +- Fixed #621. [Jamie.Rees] + + +## v1.9.5 (2016-10-27) + +### **New Features** + +- Added our own custom migrations, a lot easier to migrate DB versions now. [tidusjar] + +### **Fixes** + +- Bump version. [Jamie.Rees] + +- Small bit of work on the user claims. [Jamie.Rees] + +- Fix #612 again. [Jamie.Rees] + +- User management styling. [Jamie.Rees] + +- Fixed #608 and some other small stuff. [tidusjar] + +- More user mapping. [tidusjar] + +- Fixed #615. [tidusjar] + +- Fixed #610. [tidusjar] + +- User management stuff. [Jamie.Rees] + +- User management work. [Jamie.Rees] + +- Revert the TVSender to use the old code. [Jamie.Rees] + +- Fixed the view issue. [tidusjar] + +- S582: admin improvements part 2. [Jim MacKenzie] + +- Fix #612. [Jamie.Rees] + +- User management, migration and newsletter. [Jamie.Rees] + +- #602 recently added improvements. [tidusjar] + +- Revert "Sorting out the current state of migrations" [Jamie.Rees] + +- Sorting out the current state of migrations. [Jamie.Rees] + +- Marked as obsolete. [Jim MacKenzie] + +- Migration setup. [Jim MacKenzie] + +- Removed extra line breaks. [Jim MacKenzie] + +- Moved Newsletter Settings to its own page. [Jim MacKenzie] + +- Reverted TMDB package. [Jamie.Rees] + +- Remove DB Option. [Jamie.Rees] + +- Upgrade the movie DB package and fixed #370 To fix this I had to make another API call... It slows down the search... [tidusjar] + +- Lots of small fixes including #475. [tidusjar] + +- A better fix for #587. [tidusjar] + +- Fixed #553. [tidusjar] + +- #601. [Jamie.Rees] + +- More rework to use the Plex DB. [Jamie.Rees] + +- More work around using the PlexDatabase. [Jamie.Rees] + +- Plex DB. [Jamie.Rees] + +- Allow to process even know we had an error #578. [Jamie.Rees] + +- Fix boostrapper-datetimepicker imports (#586) [David Torosyan] + +- Potential work around for #587. [tidusjar] + +- Fixed #589. [tidusjar] + + +## v1.9.4 (2016-10-10) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added Paypalme options, no UI yet (#568) [Jim MacKenize] + +- Update appveyor.yml. [Jamie] + +- Update README.md. [Jamie] + +### **Fixes** + +- Reverted. [tidusjar] + +- Make sure it's enabled before sending the recently added. [tidusjar] + +- Moved the HR inside the table for TV Shows. [Jamie.Rees] + +- FIXED!!!!! YES BITCH! #550. [tidusjar] + +- Moved the horizontal rules inside the table row. [tidusjar] + + +## v1.9.3 (2016-10-09) + +### **New Features** + +- Added properties to disable tv requests for specific episodes or seasons and wired up to admin settings. [Matt McHughes] + +- Added different sonarr search commands. [tidusjar] + +### **Fixes** + +- Fixed #515. [tidusjar] + +- Fixed #561 and a small bit of work on #569. [tidusjar] + +- #569. [tidusjar] + +- Fixed case typo. [Matt McHughes] + +- Finished wiring tv request settings to tv search. [Matt McHughes] + +- WIP hide tv request options based on admin settings. [Matt McHughes] + +- Set meta charset to be utf-8. [Madeleine Schönemann] + +- F#552: updated labels text. [Jim MacKenize] + +- F#552: Re-design lables. [Jim MacKenzie] + +- Last correction.. Now the translation is ready to be used. [Michael Reber] + +- Forgot to correct two incorrect translations. [Michael Reber] + +- Correction of the German translation. [Michael Reber] + +- Notification improvements. [tidusjar] + +- #515. [tidusjar] + + +## v1.9.2 (2016-09-18) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update CouchPotatoCacher.cs. [Jamie] + +- Added some error handing around the GetMovie area #517. [tidusjar] + +- Added a version endpoint in "/api/version" #529. [tidusjar] + +### **Fixes** + +- Trying to fix the auto CP. [tidusjar] + +- Increase the notice message text box #527. [tidusjar] + +- #536 this should fix notification settings when it is being unsubscribed when testing. [tidusjar] + +- Improved how the TV search looks and feels. [tidusjar] + +- Fix for reverse proxy when using the wizard. [Devin Buhl] + +- Fixed #532. [tidusjar] + +- This should fix some issues with the episode requests #514. [tidusjar] + +- Small changes around existing series. [tidusjar] + +- Fixed #514 and the unit tests. [tidusjar] + +- If there is a bad password when changing it, we now inform the user. [tidusjar] + +- When logging out as admin remove the username from the session. [tidusjar] + +- Sorted out some of the UI for #18. [tidusjar] + +- Finished #18. [tidusjar] + + +## v1.9.1 (2016-08-30) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added french to the navbar. [tidusjar] + +- Changed the way we use the setTimeout function. Should fix #403 #491 #492. [tidusjar] + +- Change the redirection to use a relative uri redirect #473. [tidusjar] + +### **Fixes** + +- Fixed tests. [tidusjar] + +- Fixed #491 and added more logging around the email messages under the Info level. [tidusjar] + +- Finished #415. [tidusjar] + +- Fixed an issue where there were some JS errors on the landing page settings and stopped us being redirected to the login sometimes as an admin. [tidusjar] + +- Fixed #480. [tidusjar] + +- User management. [tidusjar] + +- Fixed #505. [tidusjar] + +- Append the application version to the end of our JS/CSS files. [tidusjar] + +- Fixed issue #487. [tidusjar] + +- Remove the datetime picker css from the main assets block and only load it on the pages it needs. #493. [tidusjar] + +- Redirect to search if we are already logged in #488. [tidusjar] + +- Fixed build. [tidusjar] + +- Fixed an issue where you could set the base url as requests #479. [tidusjar] + +- Working on the beta releases page and also the user management. [tidusjar] + +- User work. [tidusjar] + + +## v1.9.0 (2016-08-18) + +### **New Features** + +- Update the availability checker to search for TV Episodes. [tidusjar] + +- Changed the no TVMazeid message. [tidusjar] + +- Added an option to disable/enable the Plex episode cacher. [tidusjar] + +- Updated the episode cacher to have a minimum of 11 hours before it runs again. [tidusjar] + +- Added some useful analytical infomation around the wizard. [tidusjar] + +- Updated the German translations #402. [tidusjar] + +- Added some code to shrink the DB. reworked the search to speed it up. [tidusjar] + +- Change to use the GrandparentTitle rather than the thumbnail.... facepalm. [tidusjar] + +- Change the interval to hours! [tidusjar] + +- Added the transaction back into the DB. Do not run the episode cacher if it's been run in the last hour. [tidusjar] + +- Added logging. [tidusjar] + +- Changed the query slightly. [tidusjar] + +- Updated Newtonsoft.Json, Autofixture, Nlog and Dapper packages. [tidusjar] + +- Added the Sonarr check for episodes #254. [tidusjar] + +- Added unit tests. [tidusjar] + +- Added #436. [tidusjar] + +- Update build no. [tidusjar] + +- Updated translations for #402. [tidusjar] + +- Added a beta module. [tidusjar] + +- Added a custom debug root path provider, this means we do not have to recompile the views every time we make a view change. [tidusjar] + +- Update .gitignore. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added tests for the string hash. [tidusjar] + +- Added code to request the api key for CouchPotato. [tidusjar] + +- Added the file version to the layout. [tidusjar] + +- Update appveyor.yml. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added automation tests. [tidusjar] + +- Update appveyor.yml. [Jamie] + +- Updated packages. [tidusjar] + +- Updated Polly. [tidusjar] + +- Updated Fr, IT and NL translations #402. [tidusjar] + +- Changed the way the donate button works for #414. [tidusjar] + +- Added MediatR. [tidusjar] + +### **Fixes** + +- User management stuff. [tidusjar] + +- Fixed! [tidusjar] + +- Small amount of work on the user management. [tidusjar] + +- Fixed #466. [tidusjar] + +- Fixes. [tidusjar] + +- Made the episode request better. [tidusjar] + +- Removed commented out tests. [tidusjar] + +- Fixed the bad test after the merge. [tidusjar] + +- Reworked #466. [tidusjar] + +- More unit tests around the login and also the core Plex Checker. [tidusjar] + +- Potentially fixed the issue where we were requesting everything that was also available now. [tidusjar] + +- This should fix #466. [tidusjar] + +- Attempt at fixing a potential bug found from #466. [tidusjar] + +- #464 fixed. [tidusjar] + +- Fixed the build. [tidusjar] + +- Small improvements to the wizard. [tidusjar] + +- Always set the wizard to be true when editing the Plex Requests settings (Since the flag is not in the UI, a bool defaults to false). [tidusjar] + +- Tiny bit of more info. [tidusjar] + +- Finished #459. [tidusjar] + +- #459 is almost done. [tidusjar] + +- Modified the episode modal so that we are now resetting the button after a request. [tidusjar] + +- Commented out the transaction for now to debug it. [tidusjar] + +- Since we are multithreading, we should use a threadsafe type to store the episodes to prevent any threading or race conditions. [tidusjar] + +- Wrapped the bulk insert inside a transaction. [tidusjar] + +- Made the episode check parallel. [tidusjar] + +- Log out the GUID causing the issue. [tidusjar] + +- Fixed another test. [tidusjar] + +- Fixed tests. [tidusjar] + +- Got mostly everything working for #254 Ready for testing. [tidusjar] + +- Fixed issue with saving to db. [tidusjar] + +- Need to work out why the cacher is not working and where the datatype mismatch is. [tidusjar] + +- Don't delete first. [tidusjar] + +- Fix the log path issue #451. [tidusjar] + +- Dump an item. [tidusjar] + +- Small change with the return value in the batch insert. [tidusjar] + +- #254 Removed the cache, we are now storing the plex information into the database. [tidusjar] + +- Small change in the episode saver. [tidusjar] + +- Some small tweaks to improve the memory alloc. [tidusjar] + +- Short circuit when Plex hasn't been setup. Added Miniprofiler. [tidusjar] + +- Consolidate newtonsoft.json packages. [tidusjar] + +- Some performance improvements around the new TV stuff. [tidusjar] + +- Reworked the cacher, fixed the memory leak. No more logging within tight loops. [tidusjar] + +- Another null check. [tidusjar] + +- Some more changes. [tidusjar] + +- Some error handling. [tidusjar] + +- Check if the sonarr ep is monitored. [tidusjar] + +- Some logging. [tidusjar] + +- Small changes, we will actually see the episode cacher on the scheduled jobs page now. [tidusjar] + +- Work on the UI to show what episodes have been requested #254. [tidusjar] + +- Small fix. [tidusjar] + +- Fix the api change in #450. [tidusjar] + +- #254. [tidusjar] + +- Workaround for #440. [tidusjar] + +- Async async async improvements. [tidusjar] + +- Finished #266 Added a new cacher job to cache all episodes in Plex. [tidusjar] + +- Fixed #442. [tidusjar] + +- #254. [tidusjar] + +- Work around the sonarr bug #254. [tidusjar] + +- #254 having an issue with Sonarr. [tidusjar] + +- Small bit of work on #266. [tidusjar] + +- #254. [tidusjar] + +- Precheck and disable the episode boxes if we already have requested it. TODO check sonarr to see if it's already there. #254. [tidusjar] + +- Fixed broken build. [tidusjar] + +- More work for #254. [tidusjar] + +- More work on #254. [tidusjar] + +- Fixed the bug in #438 and added unit tests to make so we dont break it in the future. [tidusjar] + +- Some reason we had dupe translations. [tidusjar] + +- Rename SubDir to Base Url. [tidusjar] + +- Fix the exception in #440. [tidusjar] + +- Reworking the login page for #426. [tidusjar] + +- Fixed #438. [tidusjar] + +- Finished the auth stuff. [tidusjar] + +- Finished up the SMTP side of #429. [tidusjar] + +- #428 Added a message when the we cannot get a TVMaze ID. [tidusjar] + +- #254 MOSTLY DONE! At last, this took a while. [tidusjar] + +- Removed the other rootpath provider. [TidusJar] + +- Removed the other rootpath provider. [TidusJar] + +- Removed the other rootpath provider. [TidusJar] + +- Should fix #429. [TidusJar] + +- Done #135 We are including the application version number in the directory. [tidusjar] + +- #387 trim the spaces from the api key. Tidied up the setting models a bit. [tidusjar] + +- Wrapped the repo to catch Sqlite corrupt messages. [tidusjar] + +- Frontend and tv episodes api work for #254. [tidusjar] + +- #424. [tidusjar] + +- #359. [tidusjar] + +- Moved the plex auth token to the plex settings where it should belong. [tidusjar] + +- Small changes around the user management. [tidusjar] + +- Missed brace. [tidusjar] + +- Removed. [tidusjar] + +- Fixed issues from the merge. [tidusjar] + +- Stupid &$(*£ merge. [tidusjar] + +- Angular. [tidusjar] + +- Reworked the custom notifications... again. Need to figure out how to find the view to the model. [tidusjar] + +- Fixed #417. [tidusjar] + +- Removed NinjectConventions, we hadn't started to use it anyway. [tidusjar] + +- Fixed the way we will be using custom messages. [tidusjar] + +- Test checkin. [tidusjar] + +- Better handling for #388. [tidusjar] + +- Fixed #412. [tidusjar] + +- Fixed #413. [tidusjar] + +- Fixed #409. [tidusjar] + +- Trycatch around the availbility checker. [tidusjar] + +- WIP on notification resolver. [tidusjar] + +- Tidy. [tidusjar] + +- Plugged in MediatR. [tidusjar] + +- Moved over to using Ninject. [tidusjar] + + +## v1.8.4 (2016-06-30) + +### **Fixes** + +- Fixed the bug where we were auto approving everything. Added French language into the navigation bar. [tidusjar] + + +## v1.8.3 (2016-06-29) + +### **New Features** + +- Update README.md. [Jamie] + +- Update appveyor.yml. [Jamie] + +- Added some of the backend bits for #182. [tidusjar] + +- Updates for #243. [tidusjar] + +- Added Dutch language #243. [tidusjar] + +- Added languages #243. [tidusjar] + +- Added logging #350. [tidusjar] + +### **Fixes** + +- Small changes. [tidusjar] + +- Allow html in the notice message. [tidusjar] + +- Some more unit tests around the NotificationMessageResolver. [tidusjar] + +- Fixed a timing bug found the in build. Note, when working with time differences use TotalDays. [tidusjar] + +- More translations on the search page (Mainly the notification messages) #243. [tidusjar] + +- Fixed some warnings. [tidusjar] + +- CodeCleanup. [tidusjar] + +- Fixed a bit of a stupid bug in the resetter and added unit tests around it to make sure this never happens again. [tidusjar] + +- Fixed an issue where we didn't provide the correct response when clearing the logs. [tidusjar] + +- Made it so users that are in the whitelist do not have a request limit. [tidusjar] + +- Made it so the request limit doesn't apply to admin users. [tidusjar] + +- Fixed where a user could see the delete button on the issues page. [tidusjar] + +- Fixed some small issues and improved the navbar. [tidusjar] + +- Translated the Requested page #243. [tidusjar] + +- Finished #337. [tidusjar] + +- Some analytics. [tidusjar] + +- More translations for #243 and welcome text for #293. [tidusjar] + +- Small bit of work for #359. [tidusjar] + +- Finished #6. [tidusjar] + +- Analytics and fixes. [tidusjar] + +- Translated the search page #243. [tidusjar] + +- Implemented the different languages and added the ability to change cultures. #243. [tidusjar] + +- Started #243. [tidusjar] + +- Fixed #364. [tidusjar] + +- Some more useful analytical information. [tidusjar] + +- Generic try catch to fix #350. [tidusjar] + +- Slight changes, moved the donate button. [tidusjar] + +- Potential fix for #350. [tidusjar] + +- Better way of obtaining clean enum string. [Drewster727] + +- Fixed #362. [tidusjar] + + +## v1.8.2 (2016-06-22) + +### **New Features** + +- Update readme. [tidusjar] + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Fixed a circular reference issue. [tidusjar] + +- Small changes around how we work with custom events in the analytics. [tidusjar] + +- Fixed #353 #354 #355. [tidusjar] + +- Null provider check for movies. [Drewster727] + +- Show request type in notifications #346 and fix an issue from previous commit for #345. [Drewster727] + +- Add an option to stop sending notifications for requests that don't require approval #345. [Drewster727] + + +## v1.8.1 (2016-06-21) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Fix obj ref error when scheduler runs (ProviderId is null?) [Drewster727] + +- Fix logic for obtaining a sonarr quality profile #340. [Drewster727] + + +## v1.8.0 (2016-06-21) + +### **New Features** + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added the new advanced search into the search page too. [tidusjar] + +- Change the way we configure the IoC container in the bootstrapper, we are registering all the concrete instances on application start rather than on each web request. This should increase the performance per HTTP request. [tidusjar] + +- Updated nlog and fixed #295. [tidusjar] + +### **Fixes** + +- Workaround for #334. [Drewster727] + +- Create .gitattributes. [Jamie] + +- Fixes to the issues. [tidusjar] + +- Set the defaults for the landing page. [tidusjar] + +- Revert branch to 664dae2. [tidusjar] + +- Some unit tests for the issues. [tidusjar] + +- Tidied up the bootstrapper. [tidusjar] + +- Fix up landing page UI. [Drewster727] + +- Fixed CSS issue with the top arrow in the Plex theme. [tidusjar] + +- Small changes. [tidusjar] + +- Done #318. [tidusjar] + +- Fixed tests. [tidusjar] + +- #298 added some tests for the landing page. [tidusjar] + +- We are now only keeping the latest 1000 log records in the database. Delete everything else. [tidusjar] + +- Some analytic stuff. [tidusjar] + +- Capture the TVDBID when requesting. [tidusjar] + +- Attempting to improve #219. [tidusjar] + +- Just some more async changes. [tidusjar] + +- Small changes. [tidusjar] + +- More work on #298. Everything wired up. [tidusjar] + +- Fixed the issue on the landing page #298. [tidusjar] + +- #298 moved the content to the left a bit. [tidusjar] + +- Styling for #298 done, just need to wire up the model and do the actual status check. [tidusjar] + +- Bumped up the version number. [tidusjar] + +- Removed some DumpJson() from the trace logs. [tidusjar] + +- Small ui fix (100% width user/password fields to improve mobile experience) [Drewster727] + +- Landing page stuff #298. [tidusjar] + +- Datepicker UI fixes + small landing page UI fix. [Drewster727] + +- Removed a change that shoudn't have been commited. [tidusjar] + +- Fixed tests. [tidusjar] + +- More work for #298. [tidusjar] + +- #273 added for only available content on the search. [tidusjar] + +- Fixed #303 Looks like there was some incorrect business logic. [tidusjar] + +- Most of #273 done. [tidusjar] + +- Settings done for #298. [tidusjar] + +- Started #298. [tidusjar] + +- A crap tonne of work on #273. [tidusjar] + +- More work on #273. [tidusjar] + +- Reduced kept logs for 2 days. [tidusjar] + +- Fixed #300. [tidusjar] + +- #273. [tidusjar] + +- Fixed a bug with some users with the CP profiles. [tidusjar] + +- #273. [tidusjar] + +- Done the same for TV. [tidusjar] + +- Fixes #296. [tidusjar] + +- More for #273. [tidusjar] + +- Small changes. [tidusjar] + +- Revert "Small changes" [tidusjar] + +- Small changes. [tidusjar] + +- Finished #221 and added more async #278. [tidusjar] + +- Spelling mistake in the html! this fixes #264. [tidusjar] + +- More work on #273. [tidusjar] + +- Fixed #210. [tidusjar] + +- Started #273. [tidusjar] + + +## v1.7.5 (2016-05-29) + +### **New Features** + +- Update preview. [Jamie] + +- Updated dapper.contrib. Looks like there was a bug in the async methods. [tidusjar] + +- Updater wouldn't work when running a reverse proxy #236. [tidusjar] + +### **Fixes** + +- Bump build ver. [tidusjar] + +- Use HTTPS for the poster images, so there aren't any mixed content warnings when serving the application via an HTTPS reverse proxy. [Sean Callinan] + +- Removed static declarations. [tidusjar] + +- Fixed styling on modal. [tidusjar] + +- Made the search page all async goodness #278. [tidusjar] + +- Made the request module async #278. [tidusjar] + +- Started some dynamic scrolling. [tidusjar] + +- Stop dumping out the settings to the log. [tidusjar] + +- Made more async goodness. [tidusjar] + +- Made some of the searching async #278. [tidusjar] + +- Fixed #277. [tidusjar] + +- Reworked some tests. [tidusjar] + +- #26q make the auth users list taller. [Drewster727] + +- Fix 404 error. [Drewster727] + +- #262 make the auth users list taller. [Drewster727] + +- #221 delete requests per category. [Drewster727] + +- #256 #237 UI Improvements and consolidation. [Drewster727] + +- Fixed a bug in the user notification where if an admin wants to be notified they wouldn't be. [tidusjar] + +- Set the admin to have all claims. [tidusjar] + +- Fix null exception possibility in cp/sickrage cacher classes. [Drewster727] + +- Fixed #244. [tidusjar] + +- Fixed #240. [tidusjar] + +- Fixed #270. [tidusjar] + +- Fixed an issue where if you have only 1 plex friend it would not show in the list. [tidusjar] + + +## v1.7.4 (2016-05-25) + +### **New Features** + +- Update README.md. [Jamie] + +### **Fixes** + +- Fixed #252. [tidusjar] + +- Fixed #428. [tidusjar] + +- Version bump. [tidusjar] + +- Fixed tests. [tidusjar] + +- Fully fixed #239. [tidusjar] + +- We wan't updating the DB schema. [tidusjar] + + +## v1.7.3 (2016-05-25) + +### **Fixes** + +- Fixed the release build issue where we could not access the settings #239. [tidusjar] + + +## v1.7.2 (2016-05-25) + +### **Fixes** + +- Fixed a small bug where an exception would get thrown. [tidusjar] + +- Build version bump. [tidusjar] + +- Cleanup. [tidusjar] + +- Typo. [tidusjar] + +- Fixed #241. [tidusjar] + +- Fixed #239. [tidusjar] + +- Fixed #238. [tidusjar] + +- Small UI tweaks/improvements. [Drewster727] + + +## v1.7.1 (2016-05-24) + +### **New Features** + +- Update version. [tidusjar] + +### **Fixes** + +- Fixed an issue with the auth page when running with a reverse proxy. [tidusjar] + + +## v1.7 (2016-05-24) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added the ability to get the apikey from the api if you provide a correct username and password. Added more unit tests Added the ability to change a users password using the api refactored the Usermapper and made it unit testsable. [tidusjar] + +- Update. [tidusjar] + +- Added in an audit table. Since we are now allowing multiple users to change and modify things we need to audit this. [TidusJar] + +- Added the updater to the soloution and did a bit of starting code. [TidusJar] + +- Updated the claims so we can support more users. Added a user management section (not yet complete) Added the api to the solution and a api key in the settings (currently only gets the requests). [TidusJar] + +- Updated packages. [TidusJar] + +- Added a retry handler into the solution. We can now retry failed api requests. [TidusJar] + +- Update README.md. [Jamie] + +- Added Released propety to RequestViewModel. Added Released filter to the Requests page. [Chris Lees] + +- Added #27 to albums. [tidusjar] + +- Added the actual notification part of #27. [tidusjar] + +- Added the missing baseurl bit on the login page for #72. [tidusjar] + +- Added the 'enable user notifications' to the email settings view and model. [tidusjar] + +- Update README.md. [Jamie] + +### **Fixes** + +- Remove pointless test, change the default theme and fix a small bug. [tidusjar] + +- Fixed api. [tidusjar] + +- Finished #26. [tidusjar] + +- Plex theme. [tidusjar] + +- Implimented a theme changer, waiting for the Plex theme. [tidusjar] + +- Finished #222 #205. [tidusjar] + +- Started working on #26. [tidusjar] + +- Undid some small changes that was checked in by accident. [tidusjar] + +- #164 has been resolved. [tidusjar] + +- Resolved #224 , Removed the 'SSL' option from the email notification settings. We will now use the correct secure socket options (SSL/TLS) for your email host. [tidusjar] + +- Small changes. [tidusjar] + +- #27 fully finished. [tidusjar] + +- Fixed #215. [tidusjar] + +- Using Mailkit to fix #204. [tidusjar] + +- Color. [tidusjar] + +- Fully finished #27 just need to test it! [tidusjar] + +- Fixed test. [tidusjar] + +- Styling for #27. [tidusjar] + +- I think the auto updater is finished! #29. [tidusjar] + +- I think we have finished the main bulk of the auto updater #29. [tidusjar] + +- #222 #205 more ! Started getting the settings out. [tidusjar] + +- Removed the service locator from the base classes and added in some Api tests added all the tests back in! [tidusjar] + +- More work on the api and documentation #222 #205. [tidusjar] + +- Started documenting the API we now have swagger under ~/apidocs #222 #205. [tidusjar] + +- Api work for #205 Refactored how we check if the user has a valid api key Added POST request, PUT and DELTE. [tidusjar] + +- First pass of the updater working. #29. [tidusjar] + +- Removed SIGHUP from the termination list #220. [tidusjar] + +- Fixed. [tidusjar] + +- Missing. [tidusjar] + +- Missed out a file. [TidusJar] + +- And some more... [TidusJar] + +- Missed some files. [TidusJar] + +- A bit more work on switching to using user claims so we can support multiple users. [TidusJar] + +- Made the store backup clean up some of the older backups (> 7 days). [TidusJar] + +- More work on the user management. [TidusJar] + +- - Notifications will no longer be send to the admins if they request something. - Looks like we missed out adding the notifications to Music requests, so I added that in. [TidusJar] + +- - Improved the RetryHandler. - Made the tester buttons on the settings pages a bit more robust and added an indication when it's testing (spinner) [TidusJar] + +- Packages. [TidusJar] + +- Nm, [TidusJar] + +- Downgraded packages. [TidusJar] + +- Better handling for #202. [TidusJar] + +- Finished #208 and #202. [TidusJar] + +- This should help #202. [TidusJar] + +- Resolved #209. [TidusJar] + +- Finished #209. [TidusJar] + +- Slight adjustments to #189. [tidusjar] + +- - Added a visual indication on the UI to tell the admin there is a update available. - We are now also recording the last scheduled run in the database. [tidusjar] + +- Did the login bit on #185. [tidusjar] + +- Finished #186. [tidusjar] + +- Fixed #185. [tidusjar] + +- Fixed issue in #27 with albums. [tidusjar] + +- #27 added TV Search to the notification. [tidusjar] + +- Fixed bug. [tidusjar] + +- More work on #27 Added a new notify button to the search UI (Needs styling). Also fixed a bug where if the user could only see their own requests, if they search for something that has been requested, it will show as requested. [tidusjar] + +- Improved the startup of the application. We now properaly parse any args passed into the console. [tidusjar] + +- Additional cacher error handling + don't bother checking the requests when we don't get data back from plex. [Drewster727] + +- Remove old migration code and added new migration code. [tidusjar] + +- Stop the Cachers from bombing out when the response from the 3rd party api returns an exception or invalid response. #171. [tidusjar] + +- Increase the scheduler cache timeframe to avoid losing cache when the remote api endpoints go offline (due to a reboot or some other reason) -- if they're online, the cache will get refreshed every 10 minutes like normal. [Drewster727] + +- Fix the cacher by adding locking + extra logging in the plex checker + use a const key for scheduler caching time. [Drewster727] + +- Small changes. [tidusjar] + +- Switched out the schedulers, this seems to be a better implimentation to the previous and is easier to add new "jobs" in. [tidusjar] + +- Fixed #168. [tidusjar] + +- Fixed #162. [tidusjar] + +- Fix saving the log level. [Drewster727] + +- Set the max json length (fixes large json response errors) [Drewster727] + + +## v1.6.1 (2016-04-16) + +### **New Features** + +- Update README.md. [Jamie] + +- Added a url base. [tidusjar] + +- Change default logging. [tidusjar] + +- Added logging around SickRage. [tidusjar] + +### **Fixes** + +- Bump up the version number ready for the release. [tidusjar] + +- BaseUrl is finally finished! #72. [tidusjar] + +- #72 Login page done. [tidusjar] + +- More changes for the urlbase #72. [tidusjar] + +- Done the auth, cp, logs and sidebar for #72. [tidusjar] + +- Add an extra check when determining if a tv show is already available (also check if it starts with the show name returned from the tv db) [Drewster727] + +- Cache plex library data regardless of whether we have requests in the database or not. [Drewster727] + +- By default don't use a url base. [tidusjar] + +- Return empty array when obtaining queued IDs in sickrage cacher. [Drewster727] + +- Fixed a small bug in the SR cacher. [tidusjar] + +- Fixed when we do not have a base. [tidusjar] + +- More changes for #72. [tidusjar] + +- Fixed exception and all areas will now use the base url #72. [tidusjar] + +- Removed the test code from #72. [tidusjar] + +- Commented out the unit tests as they need to be reworked now. [tidusjar] + +- Finally fixed #72. [tidusjar] + +- Remove test code from plex api GetLibrary method. [Drewster727] + +- Finished up the caching TODO's. [tidusjar] + +- Kick off the schedulers once the web app has started (fixes api errors on start) [Drewster727] + +- Converted the UI back down to .NET 4.5.2. [tidusjar] + +- Fixed #154. [tidusjar] + +- Revert everything (except PlexRequests.UI) back to .NET 4.5.2 -- fixes incompatibilities with the latest version of mono (4.2.3.4) -- fixes notifications not working #152 #147 #141. [Drewster727] + +- #150 start caching plex media as well. refactored the availability checker. NEEDS TESTING. also, we need to make the Requests hit the plex api directly rather than hitting the cache as it does now. [Drewster727] + +- #150 split out the cache subscriptions to make sure they subscribe properly. [Drewster727] + +- #150 sonarr/sickrage cache checking. sickrage has a couple small items left. [Drewster727] + +- Fixed args. [tidusjar] + +- Fixed. [tidusjar] + +- Made the base better. [tidusjar] + +- Remove couchpotato api test code. [Drewster727] + +- Start the initial couchpotato cache call on a separate thread to keep the startup process quick. [Drewster727] + +- Add csproj with file changes from previous commit. [Drewster727] + +- Cache the couchpotato wanted list, update it on an interval, and use it to determine if a movie has been queued already. [Drewster727] + +- I think i've fixed an issue where SickRage reports Show not found. [tidusjar] + +- Set the default log level to info. #141. [tidusjar] + +- #125 refactor async task logic to work with mono. [Drewster727] + +- Fix search spinner sticking around after clearing search text + make the "Requested" and "Available" indicators in the search page different colors. [Drewster727] + +- #125 start indicating in the results if an item is already requested or available. [Drewster727] + +- #145 firefox css dsplay issue. [Drewster727] + +- Fixes for sonarr, we now display the error messages back to the user. [tidusjar] + +- Fixed #144. [tidusjar] + + +## v1.6.0 (2016-04-06) + +### **New Features** + +- Changed the build number. [tidusjar] + +- Update README.md. [Drew] + +- Update README.md. [Drew] + +- Update README.md. [Drew] + +- Update README.md. [Drew] + +- Changed the title to a contains but the artist still must match, [tidusjar] + +- Added unit tests to cover the new changes to the availability checker. [tidusjar] + +- Added the music check in the Plex Checker. [tidusjar] + +- Changed around the startup so we cache the profiles after the DB has been created. [tidusjar] + +- Updated where we update the request blobs schema change. [tidusjar] + +- Update SearchModule.cs. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Change the new columns type. [tidusjar] + +- Added a DBSchema so we have an easier way to update the DB. [tidusjar] + +- Added an issue template. [tidusjar] + +- Update README.md. [Jamie] + +- Added back the username into the Session when the admin logs in. This means they do not have to log in twice. [tidusjar] + +- Added happy path tests for the Checker. [tidusjar] + +- Added music to the search and requests page. [tidusjar] + +- Added a scroll to the top thingy and a bit more work on headphones. [tidusjar] + +- Added some tests and fixed the issue where the DB would get created in the wrong place depending on how you launched the application. [tidusjar] + +- Added the settings page for #32. [tidusjar] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update README.md. [Drewster727] + +- Update appveyor.yml. [Jamie] + +### **Fixes** + +- Some final tweaks for #32. [tidusjar] + +- Fixed a bug where if we are the admin we didn't add the request to the db. [tidusjar] + +- Fixed an issue where we would add the Sickrage series but it would fail on adding the seasons. [tidusjar] + +- Properly account for future/past dates when humanizing with moment. [Drewster727] + +- Properly display release date on requests page. [Drewster727] + +- Add missing reference for release mode. [Drewster727] + +- #139 remove dependency and usage of humanize() - should help with cross-platform issues. start using moment.js. [Drewster727] + +- Fix selectors for music list on request page to get sorting working. [Drewster727] + +- Fixed the error #32. [tidusjar] + +- Fixed the logs page. [tidusjar] + +- Another attempt at filtering #32. [tidusjar] + +- A bit more error handling #32. [tidusjar] + +- Improved the availabilty check to include music results #32. [tidusjar] + +- Small changes for #32. [tidusjar] + +- A bit more logging for #32. [tidusjar] + +- More headphones #32 I am starting to hate headphones... Sometimes the artists and albums just randomly fail. [tidusjar] + +- #134 temporary workaround for this. [Drewster727] + +- Task.run for startup caching + fix admin module unit test failures. [Drewster727] + +- Cache injection, error handling and logging on startup, etc. [Drewster727] + +- Tweaks for #32. [tidusjar] + +- #132 auto-approve for admins. [Drewster727] + +- Finished the bulk work for Headphones. Needs testing #32. [tidusjar] + +- Made the album search 10x faster. We are now loading the images in a seperate call. #32. [tidusjar] + +- Add a reference to API Interfaces to fix the build. [tidusjar] + +- #114 start caching quality profiles. Set the cache on startup and when obtaining quality profiles in settings. [Drewster727] + +- Work for #32. [tidusjar] + +- #114 first pass at choosing quality profile when approving + focus search input by default and when switching tabs. [Drewster727] + +- #131 fix for default selected tab. [Drewster727] + +- Remove references to obsolete RequestedBy property + start setting the db schema to the app version, and check that in the future for migrations. [Drewster727] + +- Fixed async issue. [Shannon Barrett] + +- Updating SickRage api to verify Season List is up to date. [Shannon Barrett] + +- Work on showing the requests for #32. [tidusjar] + +- Got the search finished up for #32. [tidusjar] + +- Remove test/temp code in UserLoginModule. [Drewster727] + +- A bit more work on #32 started working on requesting it. The DB is a bit of an issue... [tidusjar] + +- Most of the UI work done for #32. [tidusjar] + +- Basic search working for #32. [tidusjar] + +- Mono datetime offset workaround. [Drewster727] + +- #122 store utc time in the databse + obtain timezone offset of the client upon login + offset times returned to client based on session offset. [Drewster727] + +- Method reference bug fix. [Drewster727] + +- Fix search focus z-index issue (hid suggestions options) [Drewster727] + +- Minor search UI adjustments. [Drewster727] + +- #55 first attempt at "suggestions" starting with "Comming Soon" and "In Theaters" [Drewster727] + +- #106 rename sorting options and polish the dropdown UI a bit. [Drewster727] + +- Started adding the api part for headphones #32. [tidusjar] + +- Upped the time of #123. [tidusjar] + +- First attempt at #123. [tidusjar] + +- We now do not show the text Requested By to the user, we also show a 'success' message instead of a warning when something has already been requested. [tidusjar] + +- Show a "no requests yet" message on the requests page (for each cateogory) [Drewster727] + +- Ignore items that are already available when approving in bulk, and simplify the checking + compile css. [Drewster727] + +- Add a better way to merge RequestedBy and RequestedUsers to avoid code duplication and simplify checks. [Drewster727] + +- Don't query the session as much in the modules, rely on a variable from the base class and store the username as needed. [Drewster727] + +- Show the requested by user from legacy request models. [Drewster727] + +- Only show requested by users to admins + start maintaining a list of users with each request. [Drewster727] + +- #96 fix up notification test feature. [Drewster727] + +- Fix the request page sort/approve button alignment. [Drewster727] + +- When pulling requests, set each to approved that is already available (so the UI avoids showing the approve option for already available content) [Drewster727] + +- Mono doesn't seem to have Tls1.2. Let's try TLS 1 #119. [tidusjar] + +- Specify a protocol type of TLS12. Looks like CP doesn't seem to like SSL3 (it is quite old now so understandable) #119. [tidusjar] + +- Made #85 better. [tidusjar] + +- Fixed the tests. [tidusjar] + +- Made the feedback from Sonarr better when Sonarr already has the series #85. [tidusjar] + +- An attempt to fix #108. [tidusjar] + +- Add some "no results" feedback to the searching + minor UI improvements. [Drewster727] + +- Fix notification tests. [Drewster727] + +- UI - increase icon size of nav menu (they were too small before) [Drewster727] + +- #96 Finished adding test functionality to notifications. [Drewster727] + +- #96 add the necessary back-end code to produce a test message for all notification types (still have to add the test buttons for pushbullet/pushover) [Drewster727] + +- #96 modify notifications interface/service to accept a non-type specific settings object. [Drewster727] + +- #96 Email notification test button (others to come) [Drewster727] + +- Minor UI adjustments. [Drewster727] + +- #84 provide an option in settings to resttrict users from viewing requests other than their own. [Drewster727] + +- #54 comma separated list of users who don't require approval + fix a couple request messages (include show title) [Drewster727] + +- Clean up the sorting option names. add a way to see which filter/sort is currently applied. [Drewster727] + +- Fix up the animations. seems to be related to the data-bound attribute causing the animtions not to fire on each .mix object. [Drewster727] + +- Move approve buttons to the tab content. [Drewster727] + +- Allow approving all requests by category. [Drewster727] + +- Fix up sorting on the request page. [Drewster727] + +- Add ubuntu/debian instructions. [Drewster727] + +- #86 - display movie/show title + year in request notifications. [Drewster727] + +- Show the movie/show title when requesting. [Drewster727] + + +## v1.5.2 (2016-03-26) + +### **Fixes** + +- Stoped users from spamming the request button. [tidusjar] + +- Fixed the logger no longer writing to the file. [tidusjar] + +- Fixed #97. [tidusjar] + + +## v1.5.1 (2016-03-26) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Added logs to the sidebar. I'm an idiot. [tidusjar] + +### **Fixes** + +- Approve tv shows or movies. [Drewster727] + +- Fixed a bug where if you had auto approve it wouldn't notify you. [tidusjar] + + +## v1.5.0 (2016-03-25) + +### **New Features** + +- Updated version number for release. [tidusjar] + +- Updated the logic for handling specific seasons in Sonarr and Sickrage. [Shannon Barrett] + +- Updated the readme and added some icons to the navbar. [tidusjar] + +- Added the ability to sepcify a username in the email notification settings for external MTA's. We have had to add a new option called Email Sender because of this. #78. [tidusjar] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Added a notification model to the notifiers. Added the backend work for sending a notification for an issue report #75. [tidusjar] + +- Added a subdir to CP, SickRage, Sonarr and Plex #43. [tidusjar] + +### **Fixes** + +- And again. [tidusjar] + +- Made the check actually work. [tidusjar] + +- Finished up #68 and #62. [tidusjar] + +- Finished styling on the logger for now. #59. [tidusjar] + +- Fixed #69. [tidusjar] + +- Working on getting the Sonarr component to work correctly. [Shannon Barrett] + +- Fixes issue #62. [Shannon Barrett] + +- Refactored the Notification service to how it should have really been done in the first place. [tidusjar] + +- Fixed the build. [tidusjar] + +- Finished #49. [tidusjar] + +- Finished #57. [tidusjar] + +- Small changes around the filtering. [tidusjar] + +- Finished adding pushover support. #44. [tidusjar] + +- Resolved #75. [tidusjar] + +- Include DB changes. [tidusjar] + +- Done most on #59. [tidusjar] + +- Lowercase logs folder, because you know, linux. #59. [tidusjar] + +- Adding the imdb when requesting. [tidusjar] + +- Fixed an issue where the table didn't match the model. [tidusjar] + +- Improved the status page with the suggestion from #29. [tidusjar] + +- Hooked up most of #49 Just the validation messages need to be done. [tidusjar] + +- Fixed #74 and #64. [tidusjar] + +- Resolved #70. [tidusjar] + +- Finished #71. [tidusjar] + +- Got the filter working on both movie and tv #57. [tidusjar] + +- Started #57, currently there is a bug where the TV list won't filter. [tidusjar] + + +## v1.4.1 (2016-03-20) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Update AvailabilityUpdateService.cs. [Jamie] + + +## v1.4.0 (2016-03-19) + +### **New Features** + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Update README.md. [Jamie] + +- Updated the build version ready for the next release. [tidusjar] + +- Added the api and settings page for Sickrage. Just need to do the tester and hook it up #40. [tidusjar] + +- Added the option to set a CP quality #38. [tidusjar] + +- Added the code to lookup the old requests and refresh them with new information from TVMaze. [tidusjar] + +- Update StatusCheckerTests.cs. [Jamie] + +- Update README.md. [Jamie] + +- Added TVMaze to the search. #21. [tidusjar] + +- Added migration code and cleaned up the DB. [tidusjar] + +- Updated the way we add requests. [tidusjar] + +- Updated the Dapper.Contrib package, it had a bug where it wasn't returning the correct Id from inserts. [tidusjar] + +### **Fixes** + +- This fixes #36. [tidusjar] + +- Should fix issue #36. [Shannon Barrett] + +- When we do a batch update we need to reset the cache. [tidusjar] + +- Fixed an issue where the default quality on Sickrage wouldn't work. [tidusjar] + +- Wow, that was a lot of work. - So, I have now finished #40. - Fixed a bug where we was not choosing the correct tv series (Because of TVMaze) - Fixed a bug when checking for plex titles - Fixed a bug where the wrong issue would clean on the UI (DB was correct) - Refactored how we send tv shows - And too many small changes to count. [tidusjar] + +- Fixed the new dependancy with the admin class tests. [tidusjar] + +- Back to what it was :( [tidusjar] + +- Another test for #37. [tidusjar] + +- This should fix #37. [Jamie Rees] + +- Catch the missing table exception when they have a new DB. [Jamie Rees] + +- Exploratory test for #37. [Jamie Rees] + +- Fixed #33 we now have SSL options for Sonarr and CP. [Jamie Rees] + +- Removed all the html from the new TVMaze api (for overview). Added tests to cover the html removal. updated Readme to remove TheTVDB. [Jamie Rees] + +- Fixed tests. [Jamie Rees] + +- Almost fully integrated TVMaze #21 and also improved the fix for #31. [Jamie Rees] + +- Should fix #28. [Shannon Barrett] + +- Fixed #16 and #30. [tidusjar] + +- Modified the adding of request to update the model with the added ID. [tidusjar] + +- Switched over to the new service. [tidusjar] + +- Fixed #25. [Jamie Rees] + + +## v1.3.0 (2016-03-17) + +### **New Features** + +- Added pushbullet to the sidebar. [Jamie Rees] + +- Updated build version for the next release. [Jamie Rees] + +- Updated readme link. [tidusjar] + +- Added ignore to static tests. [tidusjar] + +- Added Pushbullet notifications #8. [tidusjar] + +- Added first implimentation of the Notification Service #8 Added tests to cover the notification service. [tidusjar] + +- Added validation to the Email settings, also increased the availability checker from 2 minutes to 5. [tidusjar] + +### **Fixes** + +- Fixed #22. [Jamie Rees] + +- Started on #16, nothing is hooked up yet. [tidusjar] + +- Fixed tests. [tidusjar] + + +## v1.2.1 (2016-03-16) + +### **New Features** + +- Update Program.cs. [Jamie] + +- Update Program.cs. [Jamie] + +- Added back the reference. [tidusjar] + +### **Fixes** + +- Removed the email notification settings from the settings (for release 1.2.1) [Jamie Rees] + +- Fixed. [Jamie Rees] + +- Resolved #10. [tidusjar] + + +## v1.2.0 (2016-03-15) + +### **New Features** + +- Updated. [Jamie Rees] + +- Updated appveyor. [Jamie Rees] + +- Update appveyor.yml. [Jamie] + +- Added latest version code and view. Need to finish the view #11. [tidusjar] + +- Added test button to Plex. That's fixed #9. [tidusjar] + +- Added test sonarr button #9. [tidusjar] + +- Added more tests. [tidusjar] + +- Added a bunch of logging. [tidusjar] + +- Added the application tester for CP #9. [tidusjar] + +- Added settings page for #8. [tidusjar] + +- Added pace.js. [tidusjar] + +### **Fixes** + +- Finished the notes! Resolved #7. [Jamie Rees] + +- #12. [Jamie Rees] + +- #12. [Jamie Rees] + +- Finished the status page #11 and some more work to #12. [Jamie Rees] + +- Resolved #7. [tidusjar] + +- Small changes. [tidusjar] + +- Yeah... [tidusjar] + +- Fixed #5 and also added some tests to the availability checker. [tidusjar] + +- Started added tests. [Jamie Rees] + +- Fixed an issue where the issues text appears larger. [Jamie Rees] + + +## v1.1 (2016-03-13) + +### **New Features** + +- Update appveyor.yml. [Jamie] + +- Updated readme. [Jamie Rees] + +- Added the support for TV Series integrating with Sonarr. [Jamie Rees] + +- Added the functionality to pass a port through an argument. [tidusjar] + +- Added the code to get the quality profiles from Sonarr Started plugging that into the UI. [Jamie Rees] + +- Added the spinners #3. [tidusjar] + +- Added the functionality for the admin to clear the issues. [tidusjar] + +- Added the issues to the requests page. [tidusjar] + +- Added user logout method and unit tests to cover it. [tidusjar] + +- Added DeniedUsers to the view. [tidusjar] + +- Added the denied user check to the UserLoginModule. added a test case to cover it. [tidusjar] + +- Added a missing reference. [tidusjar] + +- Added first real test. [tidusjar] + +- Update README.md. [Jamie] + +- Added the latest version of nuget. [tidusjar] + +- Added travisyml. [tidusjar] + +- Added logging. [tidusjar] + +- Added missing files. [tidusjar] + +- Update README.md. [Jamie] + +- Added logging (Still WIP) [tidusjar] + +- Added favicon and also structured the HTML correctly. [tidusjar] + +- Updated the packages so everything is now with the correct framework (4.5.2) [tidusjar] + +- Added in deletion of requests. [tidusjar] + +- Added test code. [tidusjar] + +- Added dashboard. [tidusjar] + +- Added couchpotato page. [Jamie Rees] + +- Added readme to the project and updated it. [Jamie Rees] + +- Added helpers. [tidusjar] + +### **Fixes** + +- Bug fix, Couchpotato settings wouldn't show in release due to a Nancy bug. [Jamie Rees] + +- Small changes. [Jamie Rees] + +- First release, build 1.0.0. [Jamie Rees] + +- Removed the request limit since it's not currently being used. [Jamie Rees] + +- REmoved Sickbeared for the first release. [Jamie Rees] + +- Fixed #4 We now can manually set the status of a request. [tidusjar] + +- Made the pass in the port a bit more robust. [tidusjar] + +- Styling, Added the functionality for the Sonarr Profiles on the Admin page #2 resolved. [tidusjar] + +- Fixed a bug in the Login and added a unit test to cover that. Added a button to approve an individual request. Fixed some minor bugs in the request screen. [Jamie Rees] + +- Fixed the 'responsive' issue for the search and requests pages #3. [tidusjar] + +- Styling! #3. [tidusjar] + +- Navbar category now will follow you to various screens #3. [tidusjar] + +- Fixed bugs with the 'other' reporting issue and also the clear issues. [tidusjar] + +- We now are appending the users name to who wrote the comment. Rather than it being unknown. [tidusjar] + +- More work on submitting issues. [tidusjar] + +- More test changes. [tidusjar] + +- More tests to cover the login. [tidusjar] + +- Refactoring. [tidusjar] + +- Implimented the password part and authentication with Plex. [tidusjar] + +- Initial Use authentication is working. Need to do the password bit. [tidusjar] + +- Some error handling and ensure we are an admin to delete requests. [tidusjar] + +- Fixed the issue where the Release build would not show the admin screens! [tidusjar] + +- Fixes. [tidusjar] + +- Removed the DI part of the service. TinyIOC doesn't want to work with FluentScheduler. [tidusjar] + +- First pass at the plex update service. [tidusjar] + +- Small changes. [Jamie Rees] + +- Started to impliment the Plex checker. This will check plex every x minutes to see if there is any new content and then update the avalibility of the requests. [Jamie Rees] + +- Mre work. [Jamie Rees] + +- Few small changes, added plex settings. [Jamie Rees] + +- Making the configuration actually do something. Setting a default configuration if there is no DB. [Jamie Rees] + +- Remove post build. [Jamie Rees] + +- Small changes. [Jamie Rees] + +- MOre work. [Jamie Rees] + +- Fixed the issue when sending movies to CouchPotato. [Jamie Rees] + +- Add appveyor. [tidusjar] + +- Build it on 4.5. [tidusjar] + +- Upgraded .net to 4.6. [tidusjar] + +- Typo2. [tidusjar] + +- Typo. [tidusjar] + +- Another update. [tidusjar] + +- Fixed. [tidusjar] + +- More logging to figure out why the we cannot access the admin module in a release build. [tidusjar] + +- Firstpass integrating with CouchPotato. [tidusjar] + +- Some styling. [tidusjar] + +- Fixed the plex friends. Added some unit tests, moved the plex auth into it's own page. [tidusjar] + +- Fully switched the TV shows over to use the other provider. [Jamie Rees] + +- Renamed folders. [tidusjar] + +- Assembly updates. [tidusjar] + +- Moved the rest of the projects. [tidusjar] + +- Moved UI. [tidusjar] + +- Mass rename. [tidusjar] + +- Quick changes. [tidusjar] + +- Started switching the TV over to the new provider (TheTVDB). Currently TV search is partially broken. It will search but we are not mapping all of the details. [tidusjar] + +- Implimented the new TV show Provider (needed for Sonarr TheTvDB) [tidusjar] + +- Started the user auth. [tidusjar] + +- Some work on the requests page. [tidusjar] + +- Made the 'requested' better and made the remove look nicer. [tidusjar] + +- Cleaned up the program a tiny bit. [tidusjar] + +- Removed additional namespace. [tidusjar] + +- Fixed some db issues and added a preview. [Jamie Rees] + +- More work on the settings. [Jamie Rees] + +- Upgraded Json.Net and Nancy packages. [Jamie Rees] + +- Plex friends api. [Jamie Rees] + +- Enabled trace logs. [tidusjar] + +- Sql syntax issue fixed. [tidusjar] + +- Fixed release build. [tidusjar] + +- Small updates including assembly version. [tidusjar] + +- Work on the requests page mostly done. [tidusjar] + +- Work on the TV request. the `latest` parameter is not being passed into the requestTvshow. [tidusjar] + +- Missing file. [tidusjar] + +- Using the IoC container now. [tidusjar] + +- Some plex work. [Jamie Rees] + +- More work. [Jamie Rees] + +- Removed the setup code out of the startup, since we attemtp to connect to the DB before that. [Jamie Rees] + +- Some more work. Need to stop the form submitting on a request. [tidusjar] + +- Moved everything up a directory. [tidusjar] + +- Lots of work! [tidusjar] + +- Done most of the movie search work. [Jamie Rees] + +- First pass with RequestPlex. [tidusjar] + +- Initial commit. [Jamie] + + From 0000ff1ce90ffcacbd9e8f9f8c7b692a664f763e Mon Sep 17 00:00:00 2001 From: Anojh Thayaparan Date: Mon, 16 Apr 2018 06:47:01 -0700 Subject: [PATCH 030/495] Inject base url if set before theme file url, see issue #1795 (#2148) --- src/Ombi/Controllers/SettingsController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 30469cd57..a5aef25fb 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -274,6 +274,12 @@ namespace Ombi.Controllers public async Task GetThemeContent([FromQuery]string url) { var css = await _githubApi.GetThemesRawContent(url); + var ombiSettings = await OmbiSettings(); + if (ombiSettings.BaseUrl != null) + { + int index = css.IndexOf("/api/"); + css = css.Insert(index, ombiSettings.BaseUrl); + } return Content(css, "text/css"); } From bc4db4184ce9e6dfc70aa6d0840c9584c9bcd49e Mon Sep 17 00:00:00 2001 From: Anojh Thayaparan Date: Mon, 16 Apr 2018 06:47:50 -0700 Subject: [PATCH 031/495] Knocking out LC requirements in issue #2124 (#2125) --- src/Ombi.Core/Engine/TvRequestEngine.cs | 7 +- src/Ombi.Core/Helpers/TvShowRequestBuilder.cs | 16 +++- src/Ombi.Mapping/Profiles/MovieProfile.cs | 13 ++++ src/Ombi.TheMovieDbApi/IMovieDbApi.cs | 1 + src/Ombi.TheMovieDbApi/Models/SearchResult.cs | 3 + .../Models/TvSearchResult.cs | 18 +++++ src/Ombi.TheMovieDbApi/TheMovieDbApi.cs | 13 +++- .../app/issues/issueDetails.component.html | 2 +- .../app/issues/issueDetails.component.ts | 12 ++- .../app/issues/issuestable.component.html | 8 +- .../app/issues/issuestable.component.ts | 20 ++++- .../app/requests/movierequests.component.html | 61 +++++++-------- .../app/requests/movierequests.component.ts | 47 +++++++++++- .../tvrequest-children.component.html | 15 +--- .../requests/tvrequest-children.component.ts | 16 +--- .../app/requests/tvrequests.component.html | 72 ++++++++++-------- .../app/requests/tvrequests.component.ts | 18 +++++ .../app/search/moviesearch.component.html | 2 +- .../app/search/moviesearch.component.ts | 18 +++-- .../app/search/tvsearch.component.html | 2 +- .../app/search/tvsearch.component.ts | 15 +++- src/Ombi/ClientApp/styles/Themes/plex.scss | 2 +- src/Ombi/ClientApp/styles/base.scss | 4 + src/Ombi/tsconfig.json | 2 +- .../wwwroot/images/default_movie_poster.png | Bin 0 -> 1685 bytes src/Ombi/wwwroot/images/default_tv_poster.png | Bin 0 -> 1776 bytes 26 files changed, 268 insertions(+), 119 deletions(-) create mode 100644 src/Ombi.TheMovieDbApi/Models/TvSearchResult.cs create mode 100644 src/Ombi/wwwroot/images/default_movie_poster.png create mode 100644 src/Ombi/wwwroot/images/default_tv_poster.png diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index aaa2d353d..7671c13fc 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -1,6 +1,7 @@ using System; using AutoMapper; using Ombi.Api.TvMaze; +using Ombi.Api.TheMovieDb; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Helpers; @@ -26,11 +27,12 @@ namespace Ombi.Core.Engine { public class TvRequestEngine : BaseMediaEngine, ITvRequestEngine { - public TvRequestEngine(ITvMazeApi tvApi, IRequestServiceMain requestService, IPrincipal user, + public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user, INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager, ITvSender sender, IAuditRepository audit, IRepository rl, ISettingsService settings, ICacheService cache) : base(user, requestService, rule, manager, cache, settings) { TvApi = tvApi; + MovieDbApi = movApi; NotificationHelper = helper; TvSender = sender; Audit = audit; @@ -39,6 +41,7 @@ namespace Ombi.Core.Engine private INotificationHelper NotificationHelper { get; } private ITvMazeApi TvApi { get; } + private IMovieDbApi MovieDbApi { get; } private ITvSender TvSender { get; } private IAuditRepository Audit { get; } private readonly IRepository _requestLog; @@ -47,7 +50,7 @@ namespace Ombi.Core.Engine { var user = await GetUser(); - var tvBuilder = new TvShowRequestBuilder(TvApi); + var tvBuilder = new TvShowRequestBuilder(TvApi, MovieDbApi); (await tvBuilder .GetShowInfo(tv.TvDbId)) .CreateTvList(tv) diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index 1f92536b8..6e4d20be8 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -3,7 +3,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Ombi.Api.TvMaze; +using Ombi.Api.TheMovieDb; using Ombi.Api.TvMaze.Models; +using Ombi.Api.TheMovieDb.Models; using Ombi.Core.Models.Requests; using Ombi.Core.Models.Search; using Ombi.Helpers; @@ -16,12 +18,14 @@ namespace Ombi.Core.Helpers public class TvShowRequestBuilder { - public TvShowRequestBuilder(ITvMazeApi tvApi) + public TvShowRequestBuilder(ITvMazeApi tvApi, IMovieDbApi movApi) { TvApi = tvApi; + MovieDbApi = movApi; } private ITvMazeApi TvApi { get; } + private IMovieDbApi MovieDbApi { get; } public ChildRequests ChildRequest { get; set; } public List TvRequests { get; protected set; } @@ -29,10 +33,20 @@ namespace Ombi.Core.Helpers public DateTime FirstAir { get; protected set; } public TvRequests NewRequest { get; protected set; } protected TvMazeShow ShowInfo { get; set; } + protected List Results { get; set; } public async Task GetShowInfo(int id) { ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); + Results = await MovieDbApi.SearchTv(ShowInfo.name); + foreach (TvSearchResult result in Results) { + if (result.Name == ShowInfo.name) + { + var showIds = await MovieDbApi.GetTvExternals(result.Id); + ShowInfo.externals.imdb = showIds.imdb_id; + break; + } + } DateTime.TryParse(ShowInfo.premiered, out var dt); diff --git a/src/Ombi.Mapping/Profiles/MovieProfile.cs b/src/Ombi.Mapping/Profiles/MovieProfile.cs index d7e43fc50..dd7e03379 100644 --- a/src/Ombi.Mapping/Profiles/MovieProfile.cs +++ b/src/Ombi.Mapping/Profiles/MovieProfile.cs @@ -24,6 +24,19 @@ namespace Ombi.Mapping.Profiles .ForMember(dest => dest.VoteAverage, opts => opts.MapFrom(src => src.vote_average)) .ForMember(dest => dest.VoteCount, opts => opts.MapFrom(src => src.vote_count)); + CreateMap() + .ForMember(dest => dest.BackdropPath, opts => opts.MapFrom(src => src.backdrop_path)) + .ForMember(dest => dest.Id, opts => opts.MapFrom(src => src.id)) + .ForMember(dest => dest.OriginalLanguage, opts => opts.MapFrom(src => src.original_language)) + .ForMember(dest => dest.OriginalName, opts => opts.MapFrom(src => src.original_name)) + .ForMember(dest => dest.Overview, opts => opts.MapFrom(src => src.overview)) + .ForMember(dest => dest.Popularity, opts => opts.MapFrom(src => src.popularity)) + .ForMember(dest => dest.PosterPath, opts => opts.MapFrom(src => src.poster_path)) + .ForMember(dest => dest.ReleaseDate, opts => opts.MapFrom(src => src.first_air_date)) + .ForMember(dest => dest.Name, opts => opts.MapFrom(src => src.name)) + .ForMember(dest => dest.VoteAverage, opts => opts.MapFrom(src => src.vote_average)) + .ForMember(dest => dest.VoteCount, opts => opts.MapFrom(src => src.vote_count)); + CreateMap() .ForMember(dest => dest.Adult, opts => opts.MapFrom(src => src.adult)) .ForMember(dest => dest.BackdropPath, opts => opts.MapFrom(src => src.backdrop_path)) diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index 787902a4b..5d0a89992 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -12,6 +12,7 @@ namespace Ombi.Api.TheMovieDb Task> NowPlaying(); Task> PopularMovies(); Task> SearchMovie(string searchTerm); + Task> SearchTv(string searchTerm); Task> TopRated(); Task> Upcoming(); Task> SimilarMovies(int movieId); diff --git a/src/Ombi.TheMovieDbApi/Models/SearchResult.cs b/src/Ombi.TheMovieDbApi/Models/SearchResult.cs index 81d8115f6..7b09b5e4b 100644 --- a/src/Ombi.TheMovieDbApi/Models/SearchResult.cs +++ b/src/Ombi.TheMovieDbApi/Models/SearchResult.cs @@ -32,9 +32,12 @@ namespace Ombi.TheMovieDbApi.Models public bool adult { get; set; } public string overview { get; set; } public string release_date { get; set; } + public string first_air_date { get; set; } public int?[] genre_ids { get; set; } public int id { get; set; } public string original_title { get; set; } + public string original_name { get; set; } + public string name { get; set; } public string original_language { get; set; } public string title { get; set; } public string backdrop_path { get; set; } diff --git a/src/Ombi.TheMovieDbApi/Models/TvSearchResult.cs b/src/Ombi.TheMovieDbApi/Models/TvSearchResult.cs new file mode 100644 index 000000000..eaf93d7cc --- /dev/null +++ b/src/Ombi.TheMovieDbApi/Models/TvSearchResult.cs @@ -0,0 +1,18 @@ +namespace Ombi.Api.TheMovieDb.Models +{ + public class TvSearchResult + { + public string PosterPath { get; set; } + public string Overview { get; set; } + public string ReleaseDate { get; set; } + public int?[] GenreIds { get; set; } + public int Id { get; set; } + public string OriginalName { get; set; } + public string OriginalLanguage { get; set; } + public string Name { get; set; } + public string BackdropPath { get; set; } + public float Popularity { get; set; } + public int VoteCount { get; set; } + public float VoteAverage { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 08925e490..ccd0e52e6 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -42,7 +42,18 @@ namespace Ombi.Api.TheMovieDb return await Api.Request(request); } - + + public async Task> SearchTv(string searchTerm) + { + var request = new Request($"search/tv", BaseUri, HttpMethod.Get); + request.FullUri = request.FullUri.AddQueryParameter("api_key", ApiToken); + request.FullUri = request.FullUri.AddQueryParameter("query", searchTerm); + AddRetry(request); + + var result = await Api.Request>(request); + return Mapper.Map>(result.results); + } + public async Task GetTvExternals(int theMovieDbId) { var request = new Request($"/tv/{theMovieDbId}/external_ids", BaseUri, HttpMethod.Get); diff --git a/src/Ombi/ClientApp/app/issues/issueDetails.component.html b/src/Ombi/ClientApp/app/issues/issueDetails.component.html index e88ad621c..990b9d636 100644 --- a/src/Ombi/ClientApp/app/issues/issueDetails.component.html +++ b/src/Ombi/ClientApp/app/issues/issueDetails.component.html @@ -1,5 +1,5 @@
-
+

{{issue.title}}

diff --git a/src/Ombi/ClientApp/app/issues/issueDetails.component.ts b/src/Ombi/ClientApp/app/issues/issueDetails.component.ts index 34fcbe302..1072bedbd 100644 --- a/src/Ombi/ClientApp/app/issues/issueDetails.component.ts +++ b/src/Ombi/ClientApp/app/issues/issueDetails.component.ts @@ -98,7 +98,11 @@ export class IssueDetailsComponent implements OnInit { ("url(" + x + ")"); }); this.imageService.getMoviePoster(issue.providerId).subscribe(x => { - this.posterPath = x.toString(); + if (x.length === 0) { + this.posterPath = "../../../images/default_movie_poster.png"; + } else { + this.posterPath = x.toString(); + } }); } else { @@ -107,7 +111,11 @@ export class IssueDetailsComponent implements OnInit { ("url(" + x + ")"); }); this.imageService.getTvPoster(Number(issue.providerId)).subscribe(x => { - this.posterPath = x.toString(); + if (x.length === 0) { + this.posterPath = "../../../images/default_tv_poster.png"; + } else { + this.posterPath = x.toString(); + } }); } diff --git a/src/Ombi/ClientApp/app/issues/issuestable.component.html b/src/Ombi/ClientApp/app/issues/issuestable.component.html index f98d6eb0e..83dc8a1db 100644 --- a/src/Ombi/ClientApp/app/issues/issuestable.component.html +++ b/src/Ombi/ClientApp/app/issues/issuestable.component.html @@ -1,25 +1,25 @@ - - - - "); sb.Append( " + +
+ + + + diff --git a/src/Ombi/ClientApp/app/issues/issuestable.component.ts b/src/Ombi/ClientApp/app/issues/issuestable.component.ts index ee93e689d..f03dd9a6d 100644 --- a/src/Ombi/ClientApp/app/issues/issuestable.component.ts +++ b/src/Ombi/ClientApp/app/issues/issuestable.component.ts @@ -20,11 +20,25 @@ export class IssuesTableComponent { public rowCount = 10; - public setOrder(value: string) { + public setOrder(value: string, el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + if (el.nodeName === "A") { + el = el.parentElement; + } + + const parent = el.parentElement; + const previousFilter = parent.querySelector(".active"); + if (this.order === value) { - this.reverse = !this.reverse; + this.reverse = !this.reverse; + } else { + if (previousFilter) { + previousFilter.className = ""; + } + el.className = "active"; } - + this.order = value; } diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index 6b2300f38..8eded8dba 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -67,7 +67,7 @@
- poster + poster
@@ -222,42 +222,43 @@

{{ 'Requests.Filter' | translate }}


- -

{{ 'Filter.FilterHeaderAvailability' | translate }}

-
-
- - +
+

{{ 'Filter.FilterHeaderAvailability' | translate }}

+
+
+ + +
-
-
-
- - +
+
+ + +
- -

{{ 'Filter.FilterHeaderRequestStatus' | translate }}

-
-
- - +
+

{{ 'Filter.FilterHeaderRequestStatus' | translate }}

+
+
+ + +
-
-
-
- - +
+
+ + +
-
-
-
- - +
+
+ + +
- - \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index 0fc5f6651..e28b86eaa 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -149,7 +149,16 @@ export class MovieRequestsComponent implements OnInit { event.preventDefault(); } - public clearFilter() { + public clearFilter(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; + el = el.querySelectorAll("INPUT"); + for (el of el) { + el.checked = false; + el.parentElement.classList.remove("active"); + } + this.filterDisplay = false; this.filter.availabilityFilter = FilterType.None; this.filter.statusFilter = FilterType.None; @@ -157,7 +166,8 @@ export class MovieRequestsComponent implements OnInit { this.resetSearch(); } - public filterAvailability(filter: FilterType) { + public filterAvailability(filter: FilterType, el: any) { + this.filterActiveStyle(el); this.filter.availabilityFilter = filter; this.requestService.filterMovies(this.filter) .subscribe(x => { @@ -166,7 +176,8 @@ export class MovieRequestsComponent implements OnInit { }); } - public filterStatus(filter: FilterType) { + public filterStatus(filter: FilterType, el: any) { + this.filterActiveStyle(el); this.filter.statusFilter = filter; this.requestService.filterMovies(this.filter) .subscribe(x => { @@ -190,6 +201,24 @@ export class MovieRequestsComponent implements OnInit { this.order = value; } + private filterActiveStyle(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; //gets radio div + el = el.parentElement; //gets form group div + el = el.parentElement; //gets status filter div + el = el.querySelectorAll("INPUT"); + for (el of el) { + if (el.checked) { + if (!el.parentElement.classList.contains("active")) { + el.parentElement.className += " active"; + } + } else { + el.parentElement.classList.remove("active"); + } + } + } + private loadRequests(amountToLoad: number, currentlyLoaded: number) { this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1) .subscribe(x => { @@ -243,7 +272,8 @@ export class MovieRequestsComponent implements OnInit { this.movieRequests = x; this.movieRequests.forEach((req) => { - this.movieRequests.forEach((req) => this.setBackground(req)); + this.setBackground(req); + this.setPoster(req); }); this.radarrService.getQualityProfilesFromSettings().subscribe(c => { this.radarrProfiles = c; @@ -296,11 +326,20 @@ export class MovieRequestsComponent implements OnInit { } private setOverride(req: IMovieRequests): void { + this.setPoster(req); this.setBackground(req); this.setQualityOverrides(req); this.setRootFolderOverrides(req); } + private setPoster(req: IMovieRequests): void { + if (req.posterPath === null) { + req.posterPath = "../../../images/default_movie_poster.png"; + } else { + req.posterPath = "https://image.tmdb.org/t/p/w300/" + req.posterPath; + } + } + private setBackground(req: IMovieRequests): void { req.backgroundPath = this.sanitizer.bypassSecurityTrustStyle ("url(" + "https://image.tmdb.org/t/p/w1280" + req.background + ")"); diff --git a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html index 30e1398eb..736878d4b 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html @@ -22,15 +22,7 @@
- +
@@ -101,8 +93,3 @@
- - - - \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts index 300599063..e9da2342f 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { IChildRequests, IIssueCategory } from "../interfaces"; +import { IChildRequests } from "../interfaces"; import { NotificationService, RequestService } from "../services"; @@ -13,13 +13,6 @@ export class TvRequestChildrenComponent { @Output() public requestDeleted = new EventEmitter(); - @Input() public issueCategories: IIssueCategory[]; - @Input() public issuesEnabled: boolean; - @Input() public issueProviderId: string; - public issuesBarVisible = false; - public issueRequest: IChildRequests; - public issueCategorySelected: IIssueCategory; - constructor(private requestService: RequestService, private notificationService: NotificationService) { } @@ -101,13 +94,6 @@ export class TvRequestChildrenComponent { }); } - public reportIssue(catId: IIssueCategory, req: IChildRequests) { - this.issueRequest = req; - this.issueCategorySelected = catId; - this.issuesBarVisible = true; - this.issueProviderId = req.id.toString(); - } - private removeRequestFromUi(key: IChildRequests) { const index = this.childRequests.indexOf(key, 0); if (index > -1) { diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.html b/src/Ombi/ClientApp/app/requests/tvrequests.component.html index a602d27e1..9a3c4d186 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.html @@ -64,51 +64,63 @@
- +
- -
- - - + +
+ + + +
+ + +
+ + + +
+
- - -
- - -
- -
+ (requestDeleted)="childRequestDeleted($event)">
+ + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts index ad8b4ca50..344bf5712 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts @@ -33,6 +33,9 @@ export class TvRequestsComponent implements OnInit { @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; public issueProviderId: string; + public issuesBarVisible = false; + public issueRequest: ITvRequests; + public issueCategorySelected: IIssueCategory; public sonarrProfiles: ISonarrProfile[] = []; public sonarrRootFolders: ISonarrRootFolder[] = []; @@ -151,6 +154,13 @@ export class TvRequestsComponent implements OnInit { this.updateRequest(searchResult); } + public reportIssue(catId: IIssueCategory, req: ITvRequests) { + this.issueRequest = req; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.id.toString(); + } + private setOverride(req: ITvRequests): void { this.setQualityOverrides(req); this.setRootFolderOverrides(req); @@ -191,6 +201,7 @@ export class TvRequestsComponent implements OnInit { .subscribe(x => { this.tvRequests = x; this.tvRequests.forEach((val, index) => { + this.setDefaults(val); this.loadBackdrop(val); this.setOverride(val.data); }); @@ -209,6 +220,13 @@ export class TvRequestsComponent implements OnInit { this.currentlyLoaded = 5; this.loadInit(); } + + private setDefaults(val: any) { + if (val.data.posterPath === null) { + val.data.posterPath = "../../../images/default_tv_poster.png"; + } + } + private loadBackdrop(val: TreeNode): void { this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => { val.data.background = this.sanitizer.bypassSecurityTrustStyle diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 58d8415fb..cac78531b 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -33,7 +33,7 @@
- poster + poster
diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 819463c4f..0abc10474 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -157,12 +157,15 @@ export class MovieSearchComponent implements OnInit { private getExtraInfo() { - this.movieResults.forEach((val, index) => { - - val.background = this.sanitizer. - bypassSecurityTrustStyle - ("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")"); - this.searchService.getMovieInformation(val.id) + this.movieResults.forEach((val, index) => { + if (val.posterPath === null) { + val.posterPath = "../../../images/default_movie_poster.png"; + } else { + val.posterPath = "https://image.tmdb.org/t/p/w300/" + val.posterPath; + } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")"); + this.searchService.getMovieInformation(val.id) .subscribe(m => { this.updateItem(val, m); }); @@ -174,7 +177,8 @@ export class MovieSearchComponent implements OnInit { if (index > -1) { const copy = { ...this.movieResults[index] }; this.movieResults[index] = updated; - this.movieResults[index].background = copy.background; + this.movieResults[index].background = copy.background; + this.movieResults[index].posterPath = copy.posterPath; } } private clearResults() { diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.html b/src/Ombi/ClientApp/app/search/tvsearch.component.html index fb1729e50..42232a564 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.html +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.html @@ -62,7 +62,7 @@
- +

{{node.data.title}} ({{node.data.firstAired | date: 'yyyy'}})

diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index 961e85f63..bb30810e4 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -130,7 +130,6 @@ export class TvSearchComponent implements OnInit { public getExtraInfo() { this.tvResults.forEach((val, index) => { this.imageService.getTvBanner(val.data.id).subscribe(x => { - val.data.background = this.sanitizer. bypassSecurityTrustStyle ("url(" + x + ")"); @@ -138,6 +137,7 @@ export class TvSearchComponent implements OnInit { this.searchService.getShowInformationTreeNode(val.data.id) .subscribe(x => { if (x.data) { + this.setDefaults(x); this.updateItem(val, x); } else { const index = this.tvResults.indexOf(val, 0); @@ -216,6 +216,7 @@ export class TvSearchComponent implements OnInit { const index = this.tvResults.indexOf(key, 0); if (index > -1) { // Update certain properties, otherwise we will loose some data + this.tvResults[index].data.title = updated.data.title; this.tvResults[index].data.banner = updated.data.banner; this.tvResults[index].data.imdbId = updated.data.imdbId; this.tvResults[index].data.seasonRequests = updated.data.seasonRequests; @@ -225,6 +226,18 @@ export class TvSearchComponent implements OnInit { } } + private setDefaults(x: any) { + if (x.data.banner === null) { + x.data.banner = "../../../images/default_tv_poster.png"; + } + + if (x.data.imdbId === null) { + x.data.imdbId = "https://www.tvmaze.com/shows/" + x.data.seriesId; + } else { + x.data.imdbId = "http://www.imdb.com/title/" + x.data.imdbId + "/"; + } + } + private clearResults() { this.tvResults = []; this.searchApplied = false; diff --git a/src/Ombi/ClientApp/styles/Themes/plex.scss b/src/Ombi/ClientApp/styles/Themes/plex.scss index 1e3c27eaf..1475aef91 100644 --- a/src/Ombi/ClientApp/styles/Themes/plex.scss +++ b/src/Ombi/ClientApp/styles/Themes/plex.scss @@ -351,5 +351,5 @@ button.list-group-item:focus { position: absolute; } table.table > thead > tr > th.active { - background-color: transparent; + background-color: $primary-colour; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index 619dd8042..3a5deb2a3 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -541,6 +541,10 @@ $border-radius: 10px; cursor: pointer; } +.table-usermanagement { + margin-top: 20px; +} + .input-group-sm { padding-top: 2px; padding-bottom: 2px; diff --git a/src/Ombi/tsconfig.json b/src/Ombi/tsconfig.json index daff6b451..8f860760c 100644 --- a/src/Ombi/tsconfig.json +++ b/src/Ombi/tsconfig.json @@ -11,7 +11,7 @@ "noUnusedLocals": true, "noImplicitThis": true, "noImplicitReturns": true, - "noImplicitAny": true, + "noImplicitAny": false, "suppressImplicitAnyIndexErrors": true, "alwaysStrict": true, "emitDecoratorMetadata": true, diff --git a/src/Ombi/wwwroot/images/default_movie_poster.png b/src/Ombi/wwwroot/images/default_movie_poster.png new file mode 100644 index 0000000000000000000000000000000000000000..f5cec85a5b313321a2840ebbb07c5bd52843678b GIT binary patch literal 1685 zcmeAS@N?(olHy`uVBq!ia0vp^Cm0wQbvW37thQf&_AxN9#dx|nhE&XXd)MDzDq80F zL*t5{{xK7>axU-q_ILjhy;;k0V#ong(>C9n;y>TcRBr!{9U9FC7hEZgUAKPy>9oz2N3ZUcz5BDX>_Uy5{^px& zg8BbUzw-L)sT?!y&p-d%@P4`G|8xI;tv+g#2^mm8;lT*mq2z`OVFPiJklx^Z2ehb`4aO7_j`|1VjJ98Nzq ziu+lm`TqWX{lgDGJh=bdUuA;roqPA}ZZ?O6hN?~Wl#tSm3gzYH zOV_>LbT_a5_xFn#AwX}hUh{UXu!r~Sue)mg|Eqnz@%HW8MU|B|SN8rY+pQbFzphru zUDN)WwG5y4{`>ZaPTdomt{+uZQITP@?QUMVok0J~ou*5FAGU`l*UXnso-Fwo5gI!8 z)`IDu{>-}c_o4i{q^(!pe?MJgcmL<2`zb~*QxwwE(}C_#KhDnl7p?U$vM_s-&JKO7W#)iK^>&8k&}e6QcVTUU7J?g~$9 zrcBM=Svq3fa~G@qt=nJo=f}qd#*u&P>Ys~hE>_cbR?yulX?n9%_4Sh{Pc){7-0qph z@ucz_G)*P&U%Iqk*0i#s!s3+L-{-SteN%Mvy(Q@34K!&__4mB><))GAuRs0#{5<>q zGru1mZdaFnw`$d?pFb-fr^ZQtDP!5xelus=_1EmDpMHAqe`40|yVIT)?flGWFV|nb z&(66TV$br3>K!|GX3CVme{>}$_ImH*clo(SKOrvcm4Wx>weSTSu&S_fqj2p zZT21J-s8#AU&ZDmZG95!`_Bhn2y6j!K>=x!_3883)w?B5fE+SAe0S5;K8J4`-mf^B zZObuxUw`1_|9%SWCNg}dj~+dG(B9#Adb3QQ10OJ@{bX5T?xO&AO**5VSog8Q^2KUz zIaF@snC-V?EZZ7o+BfS;>D`^Zzs?6vj(xT3-`mM~=CjXc>%H|YoM~QR^WyuR<`vzG z)lNSCy*YcqM0uNL7W?^zyDV1z7BZXdyYls}k25C*ty;Z$b>81s&3uJjtfPe(JPIi& c&j0Yl zO)W`OsL0L9E4HezRRWu91!RMS^_3LBN=mYAl^~9a@C{IK&M!(;Fx4~DO*S+!QZTpF zGc+@>G&0dqFfuSS*EcZNH#F8YFtjo-vof|+fC43;ZAB?*RzWUqP`iLUTcwPWk^(Dz z{qpj1y>er{{GxPyLrY6beFGzXBO_g)3fZE`@j@w*YQzUNJP7fB~jokyxN_sAmB35=^15FMg%Dxp39RB|)hO_hL1; zIHa;5RX-@TIKQ+g85nVC${?!>telHd6HD@oLh|!-V4)b0kzbNuoRMFk;OwlR5uTZs zl3!k|30CgwYvq|&T#}fVoa*Ufs{}MaFEca6%FM*V$kfEu*x1s*($LV=!qLdW)zQSn z+1SzCz{Sbc5~kNBKe;qFHLnDwHwB^B1gBn5O2{n$+U$~Alv$RV;#QQOs{r<~RVHq? z7~(V!sy799yBiO3djj(>Wj6+5Kc& z-Lg|`mm~G2%U{@X#JgdGfY>3XL`9tz)q1Rudu9rqma}8`ceyFN<)w9o)+i&OooDey! z*KM&u`n=~Kw~9Utl2{k2yZ+T`gDi;~a<5riL@yjTRCVChyk%@+*POTMvu&%aZ@ZdR zx-;h1r$E0+N0T1j=QZ4MrzSzug{Y8fVdeiIW*^}@>4?Fhtb5jfmfQ{M*bsO5Wr@*- z4YR_hEp2SQx@VEbs#R4}GY_vweDLF5Yy9MsD%ZNhW-ZJ8#}#({YuWC*IcD!>{V_Te z{q^^|9&*r<0+{(ORQ@Dylq8^mODt z#fC-{YMuVH{NCLS_4nVF9qJ0c;qtt)&PHy2&B9fywB~%TvHKk+u@)G_aqHKfUWb`> jI8G1w)b;;eKLay^g>(Pw(m4fvpgPFY)z4*}Q$iB}QrLNN literal 0 HcmV?d00001 From 7bbcb9a6267df751c47d2b91e2ee5da0d8c0fe65 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Wed, 18 Apr 2018 15:11:44 +0100 Subject: [PATCH 032/495] Made an attempt at PlexOAuth !wip --- src/Ombi/Controllers/PlexOAuthController.cs | 40 +++++++++ src/Ombi/Controllers/TokenController.cs | 95 ++++++++++++--------- src/Ombi/Models/UserAuthModel.cs | 1 + 3 files changed, 97 insertions(+), 39 deletions(-) create mode 100644 src/Ombi/Controllers/PlexOAuthController.cs diff --git a/src/Ombi/Controllers/PlexOAuthController.cs b/src/Ombi/Controllers/PlexOAuthController.cs new file mode 100644 index 000000000..8f33e37be --- /dev/null +++ b/src/Ombi/Controllers/PlexOAuthController.cs @@ -0,0 +1,40 @@ +using System.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http.Internal; +using System.IO; +using System.Text; + +namespace Ombi.Controllers +{ + [ApiExplorerSettings(IgnoreApi = true)] + [ApiV1] + [AllowAnonymous] + public class PlexOAuthController : Controller + { + + [HttpGet] + public IActionResult OAuthCallBack() + { + var bodyStr = ""; + var req = Request; + + // Allows using several time the stream in ASP.Net Core + req.EnableRewind(); + + // Arguments: Stream, Encoding, detect encoding, buffer size + // AND, the most important: keep stream opened + using (StreamReader reader + = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true)) + { + bodyStr = reader.ReadToEnd(); + } + + // Rewind, so the core is not lost when it looks the body for the request + req.Body.Position = 0; + + // Do your work with bodyStr + return Ok(); + } + } +} diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 18da61e3a..5d524b7ba 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; +using System.Net.Http; using System.Security.Claims; using System.Text; using System.Threading.Tasks; @@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using Ombi.Api; using Ombi.Core.Authentication; using Ombi.Helpers; using Ombi.Models; @@ -62,50 +64,65 @@ namespace Ombi.Controllers user.EmailLogin = true; } - // Verify Password - if (await _userManager.CheckPasswordAsync(user, model.Password)) + if (!model.UsePlexOAuth) { - var roles = await _userManager.GetRolesAsync(user); - - if (roles.Contains(OmbiRoles.Disabled)) + // Verify Password + if (await _userManager.CheckPasswordAsync(user, model.Password)) { - return new UnauthorizedResult(); + var roles = await _userManager.GetRolesAsync(user); + + if (roles.Contains(OmbiRoles.Disabled)) + { + return new UnauthorizedResult(); + } + + user.LastLoggedIn = DateTime.UtcNow; + await _userManager.UpdateAsync(user); + + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Sub, user.UserName), + new Claim(ClaimTypes.NameIdentifier, user.Id), + new Claim(ClaimTypes.Name, user.UserName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + claims.AddRange(roles.Select(role => new Claim("role", role))); + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + + var token = new JwtSecurityToken( + claims: claims, + expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), + signingCredentials: creds, + audience: "Ombi", issuer: "Ombi" + ); + var accessToken = new JwtSecurityTokenHandler().WriteToken(token); + if (model.RememberMe) + { + // Save the token so we can refresh it later + //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); + } + + return new JsonResult(new + { + access_token = accessToken, + expiration = token.ValidTo + }); } + } + else + { + // Plex OAuth + // Redirect them to Plex - user.LastLoggedIn = DateTime.UtcNow; - await _userManager.UpdateAsync(user); - - var claims = new List - { - new Claim(JwtRegisteredClaimNames.Sub, user.UserName), - new Claim(ClaimTypes.NameIdentifier, user.Id), - new Claim(ClaimTypes.Name, user.UserName), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) - }; - claims.AddRange(roles.Select(role => new Claim("role", role))); - - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); - var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - - - var token = new JwtSecurityToken( - claims: claims, - expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), - signingCredentials: creds, - audience: "Ombi", issuer:"Ombi" - ); - var accessToken = new JwtSecurityTokenHandler().WriteToken(token); - if (model.RememberMe) - { - // Save the token so we can refresh it later - //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); - } + var request = new Request("auth", "https://app.plex.tv", HttpMethod.Get); + request.AddQueryString("clientID", "OMBIv3"); + request.AddQueryString("forwardUrl", "http://localhost:5000"); + request.AddQueryString("context-device-product", "http://localhost:5000"); + return new RedirectResult("https://app.plex.tv/auth#?forwardUrl=http://localhost:5000/api/v1/plexoauth&clientID=OMBIv3&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO"); - return new JsonResult(new - { - access_token = accessToken, - expiration = token.ValidTo - }); } return new UnauthorizedResult(); diff --git a/src/Ombi/Models/UserAuthModel.cs b/src/Ombi/Models/UserAuthModel.cs index 5b3d69a29..046119821 100644 --- a/src/Ombi/Models/UserAuthModel.cs +++ b/src/Ombi/Models/UserAuthModel.cs @@ -6,5 +6,6 @@ public string Password { get; set; } public bool RememberMe { get; set; } public bool UsePlexAdminAccount { get; set; } + public bool UsePlexOAuth { get; set; } } } \ No newline at end of file From bd741e053f5bc764ce95180bba23370fdad1041b Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Wed, 18 Apr 2018 15:15:44 +0100 Subject: [PATCH 033/495] Fixed #2169 --- src/Ombi.DependencyInjection/IocExtensions.cs | 1 - src/Ombi.Schedule/JobSetup.cs | 4 +- .../Plex/Interfaces/IPlexRecentlyAddedSync.cs | 9 ----- .../Jobs/Plex/PlexRecentlyAddedSync.cs | 40 ------------------- .../app/settings/jobs/jobs.component.html | 2 +- 5 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs delete mode 100644 src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index e2f667465..92ecf8282 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -175,7 +175,6 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); - services.AddTransient(); } } } diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index 1e78d226c..6626ef933 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -19,7 +19,7 @@ namespace Ombi.Schedule IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter, IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache, ISettingsService jobsettings, ISickRageSync srSync, IRefreshMetadata refresh, - INewsletterJob newsletter, IPlexRecentlyAddedSync recentlyAddedSync) + INewsletterJob newsletter) { _plexContentSync = plexContentSync; _radarrSync = radarrSync; @@ -33,7 +33,6 @@ namespace Ombi.Schedule _srSync = srSync; _refreshMetadata = refresh; _newsletter = newsletter; - _plexRecentlyAddedSync = recentlyAddedSync; } private readonly IPlexContentSync _plexContentSync; @@ -48,7 +47,6 @@ namespace Ombi.Schedule private readonly ISettingsService _jobSettings; private readonly IRefreshMetadata _refreshMetadata; private readonly INewsletterJob _newsletter; - private readonly IPlexRecentlyAddedSync _plexRecentlyAddedSync; public void Setup() { diff --git a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs b/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs deleted file mode 100644 index 6616f29bc..000000000 --- a/src/Ombi.Schedule/Jobs/Plex/Interfaces/IPlexRecentlyAddedSync.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Threading.Tasks; - -namespace Ombi.Schedule.Jobs.Plex -{ - public interface IPlexRecentlyAddedSync : IBaseJob - { - Task Start(); - } -} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs deleted file mode 100644 index dfcb9cac1..000000000 --- a/src/Ombi.Schedule/Jobs/Plex/PlexRecentlyAddedSync.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Threading.Tasks; -using Ombi.Api.Plex; - -namespace Ombi.Schedule.Jobs.Plex -{ - public class PlexRecentlyAddedSync : IPlexRecentlyAddedSync - { - public PlexRecentlyAddedSync(IPlexContentSync contentSync) - { - _sync = contentSync; - } - - private readonly IPlexContentSync _sync; - - public async Task Start() - { - await _sync.CacheContent(true); - } - - private bool _disposed; - protected virtual void Dispose(bool disposing) - { - if (_disposed) - return; - - if (disposing) - { - _sync?.Dispose(); - } - _disposed = true; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - } -} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html index 72b78a64d..8b9eb89f5 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html @@ -59,7 +59,7 @@
- + The Plex Sync is required
From 025797c1baebe7bf830d50f8caf82c84d659f501 Mon Sep 17 00:00:00 2001 From: Anojh Thayaparan Date: Thu, 19 Apr 2018 00:05:49 -0700 Subject: [PATCH 034/495] added background property to tvrequests API (#2172) * Added background property to TvRequests * set background property for new requests and display in UI --- src/Ombi.Core/Engine/TvRequestEngine.cs | 1 + src/Ombi.Core/Helpers/TvShowRequestBuilder.cs | 5 +- src/Ombi.Helpers/PosterPathHelper.cs | 14 + .../Entities/Requests/TvRequests.cs | 1 + ...413021646_tvrequestsbackground.Designer.cs | 950 ++++++++++++++++++ .../20180413021646_tvrequestsbackground.cs | 24 + .../Migrations/OmbiContextModelSnapshot.cs | 2 + .../app/requests/tvrequests.component.ts | 9 +- 8 files changed, 1003 insertions(+), 3 deletions(-) create mode 100644 src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.Designer.cs create mode 100644 src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.cs diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 7671c13fc..44eec3fd1 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -277,6 +277,7 @@ namespace Ombi.Core.Engine results.ImdbId = request.ImdbId; results.Overview = request.Overview; results.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath); + results.Background = PosterPathHelper.FixBackgroundPath(request.Background); results.QualityOverride = request.QualityOverride; results.RootFolder = request.RootFolder; diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs index 6e4d20be8..0b7d1ee6e 100644 --- a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -30,6 +30,7 @@ namespace Ombi.Core.Helpers public ChildRequests ChildRequest { get; set; } public List TvRequests { get; protected set; } public string PosterPath { get; protected set; } + public string BackdropPath { get; protected set; } public DateTime FirstAir { get; protected set; } public TvRequests NewRequest { get; protected set; } protected TvMazeShow ShowInfo { get; set; } @@ -44,6 +45,7 @@ namespace Ombi.Core.Helpers { var showIds = await MovieDbApi.GetTvExternals(result.Id); ShowInfo.externals.imdb = showIds.imdb_id; + BackdropPath = result.BackdropPath; break; } } @@ -240,7 +242,8 @@ namespace Ombi.Core.Helpers ImdbId = ShowInfo.externals?.imdb ?? string.Empty, TvDbId = tv.TvDbId, ChildRequests = new List(), - TotalSeasons = tv.Seasons.Count() + TotalSeasons = tv.Seasons.Count(), + Background = BackdropPath }; NewRequest.ChildRequests.Add(ChildRequest); diff --git a/src/Ombi.Helpers/PosterPathHelper.cs b/src/Ombi.Helpers/PosterPathHelper.cs index 7de767e44..ed1dee5c0 100644 --- a/src/Ombi.Helpers/PosterPathHelper.cs +++ b/src/Ombi.Helpers/PosterPathHelper.cs @@ -18,5 +18,19 @@ namespace Ombi.Helpers return poster; } + + public static string FixBackgroundPath(string background) + { + // https://image.tmdb.org/t/p/w1280/fJAvGOitU8y53ByeHnM4avtKFaG.jpg + + if (background.Contains("image.tmdb.org", CompareOptions.IgnoreCase)) + { + // Somehow we have a full path here for the poster, we only want the last segment + var backgroundSegments = background.Split('/'); + return backgroundSegments.Last(); + } + + return background; + } } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/Requests/TvRequests.cs b/src/Ombi.Store/Entities/Requests/TvRequests.cs index 88e1c36d7..7a6abc792 100644 --- a/src/Ombi.Store/Entities/Requests/TvRequests.cs +++ b/src/Ombi.Store/Entities/Requests/TvRequests.cs @@ -13,6 +13,7 @@ namespace Ombi.Store.Entities.Requests public string Overview { get; set; } public string Title { get; set; } public string PosterPath { get; set; } + public string Background { get; set; } public DateTime ReleaseDate { get; set; } public string Status { get; set; } /// diff --git a/src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.Designer.cs b/src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.Designer.cs new file mode 100644 index 000000000..b79528e34 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.Designer.cs @@ -0,0 +1,950 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180413021646_tvrequestsbackground")] + partial class tvrequestsbackground + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + 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.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + 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.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.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + 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.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + 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.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.cs b/src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.cs new file mode 100644 index 000000000..d385d6000 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180413021646_tvrequestsbackground.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class tvrequestsbackground : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Background", + table: "TvRequests", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Background", + table: "TvRequests"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 19e14a4ca..b722b0d30 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -645,6 +645,8 @@ namespace Ombi.Store.Migrations b.Property("Id") .ValueGeneratedOnAdd(); + b.Property("Background"); + b.Property("ImdbId"); b.Property("Overview"); diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts index 344bf5712..1ce5f0b8b 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts @@ -228,9 +228,14 @@ export class TvRequestsComponent implements OnInit { } private loadBackdrop(val: TreeNode): void { - this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => { + if (val.data.background != null) { val.data.background = this.sanitizer.bypassSecurityTrustStyle - ("url(" + x + ")"); + ("url(https://image.tmdb.org/t/p/w1280" + val.data.background + ")"); + } else { + this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => { + val.data.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + x + ")"); }); + } } } From 2fc4ef2613a1e26c30e24e4e8ed6205ba30d04f0 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Thu, 19 Apr 2018 14:26:22 +0100 Subject: [PATCH 035/495] Got plex oauth working! !wip --- src/Ombi.Api.Plex/IPlexApi.cs | 7 +- src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs | 27 ++++ src/Ombi.Api.Plex/PlexApi.cs | 79 ++++++++++- src/Ombi.Api/Request.cs | 4 +- .../Authentication/PlexOAuthManager.cs | 76 ++++++++++ src/Ombi.Core/IPlexOAuthManager.cs | 16 +++ src/Ombi.DependencyInjection/IocExtensions.cs | 2 + .../Settings/Models/External/PlexSettings.cs | 2 +- src/Ombi/ClientApp/app/auth/auth.service.ts | 4 + src/Ombi/ClientApp/app/interfaces/IPlex.ts | 4 + .../app/services/applications/index.ts | 1 + .../app/services/applications/plex.service.ts | 4 + .../applications/plexoauth.service.ts | 20 +++ .../app/wizard/plex/plex.component.html | 7 + .../app/wizard/plex/plex.component.ts | 8 ++ .../app/wizard/plex/plexoauth.component.html | 14 ++ .../app/wizard/plex/plexoauth.component.ts | 76 ++++++++++ .../ClientApp/app/wizard/wizard.module.ts | 5 + .../Controllers/External/PlexController.cs | 38 ++++- src/Ombi/Controllers/PlexOAuthController.cs | 72 +++++++--- src/Ombi/Controllers/SettingsController.cs | 8 +- src/Ombi/Controllers/TokenController.cs | 133 +++++++++++------- 22 files changed, 534 insertions(+), 73 deletions(-) create mode 100644 src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs create mode 100644 src/Ombi.Core/Authentication/PlexOAuthManager.cs create mode 100644 src/Ombi.Core/IPlexOAuthManager.cs create mode 100644 src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts create mode 100644 src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html create mode 100644 src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 0734262cf..cc61dfa5d 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models.Friends; +using Ombi.Api.Plex.Models.OAuth; using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Status; @@ -20,5 +22,8 @@ namespace Ombi.Api.Plex Task GetUsers(string authToken); Task GetAccount(string authToken); Task GetRecentlyAdded(string authToken, string uri, string sectionId); + Task CreatePin(); + Task GetPin(int pinId); + Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs b/src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs new file mode 100644 index 000000000..e65cd91d4 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/OAuth/OAuthPin.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ombi.Api.Plex.Models.OAuth +{ + public class OAuthPin + { + public int id { get; set; } + public string code { get; set; } + public bool trusted { get; set; } + public string clientIdentifier { get; set; } + public Location location { get; set; } + public int expiresIn { get; set; } + public DateTime createdAt { get; set; } + public DateTime expiresAt { get; set; } + public string authToken { get; set; } + } + + public class Location + { + public string code { get; set; } + public string country { get; set; } + public string city { get; set; } + public string subdivisions { get; set; } + public string coordinates { get; set; } + } + +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index e6c52d1df..5fb87aca7 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -1,20 +1,49 @@ -using System.Net.Http; +using System; +using System.Net.Http; +using System.Reflection; using System.Threading.Tasks; using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models.Friends; +using Ombi.Api.Plex.Models.OAuth; using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Status; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; namespace Ombi.Api.Plex { public class PlexApi : IPlexApi { - public PlexApi(IApi api) + public PlexApi(IApi api, ISettingsService settings) { Api = api; + _plex = settings; } private IApi Api { get; } + private readonly ISettingsService _plex; + + private string _clientId; + private string ClientIdSecret + { + get + { + if (string.IsNullOrEmpty(_clientId)) + { + var settings = _plex.GetSettings(); + if (settings.UniqueInstallCode.IsNullOrEmpty()) + { + settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); + _plex.SaveSettings(settings); + } + + _clientId = settings.UniqueInstallCode; + } + + return _clientId; + } + } private const string SignInUri = "https://plex.tv/users/sign_in.json"; private const string FriendsUri = "https://plex.tv/pms/friends/all"; @@ -156,6 +185,50 @@ namespace Ombi.Api.Plex return await Api.Request(request); } + public async Task CreatePin() + { + var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post); + request.AddQueryString("strong", "true"); + AddHeaders(request); + + return await Api.Request(request); + } + + public async Task GetPin(int pinId) + { + var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get); + AddHeaders(request); + + return await Api.Request(request); + } + + public Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard) + { + var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get); + AddHeaders(request); + var forwardUrl = wizard + ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) + : new Request($"api/v1/PlexOAuth/{pinId}", applicationUrl, HttpMethod.Get); + + request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); + request.AddQueryString("pinID", pinId.ToString()); + request.AddQueryString("code", code); + request.AddQueryString("context[device][product]", "Ombi"); + request.AddQueryString("context[device][environment]", "bundled"); + request.AddQueryString("clientID", $"OmbiV3{ClientIdSecret}"); + + if (request.FullUri.Fragment.Equals("#")) + { + var uri = request.FullUri.ToString(); + var withoutEnd = uri.Remove(uri.Length - 1, 1); + var startOfQueryLocation = withoutEnd.IndexOf('?'); + var better = withoutEnd.Insert(startOfQueryLocation, "#"); + request.FullUri = new Uri(better); + } + + return request.FullUri; + } + /// /// Adds the required headers and also the authorization header /// @@ -173,7 +246,7 @@ namespace Ombi.Api.Plex /// private void AddHeaders(Request request) { - request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3"); + request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3{ClientIdSecret}"); request.AddHeader("X-Plex-Product", "Ombi"); request.AddHeader("X-Plex-Version", "3"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); diff --git a/src/Ombi.Api/Request.cs b/src/Ombi.Api/Request.cs index e4120ed9c..89c3a7f2d 100644 --- a/src/Ombi.Api/Request.cs +++ b/src/Ombi.Api/Request.cs @@ -10,7 +10,7 @@ namespace Ombi.Api { public Request() { - + } public Request(string endpoint, string baseUrl, HttpMethod http, ContentType contentType = ContentType.Json) @@ -105,10 +105,10 @@ namespace Ombi.Api hasQuery = true; startingTag = builder.Query.Contains("?") ? "&" : "?"; } - builder.Query = hasQuery ? $"{builder.Query}{startingTag}{key}={value}" : $"{startingTag}{key}={value}"; + _modified = builder.Uri; } diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs new file mode 100644 index 000000000..d3bab0a05 --- /dev/null +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -0,0 +1,76 @@ +using System; +using System.Threading.Tasks; +using Ombi.Api.Plex; +using Ombi.Api.Plex.Models; +using Ombi.Api.Plex.Models.OAuth; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models; + +namespace Ombi.Core.Authentication +{ + public class PlexOAuthManager : IPlexOAuthManager + { + public PlexOAuthManager(IPlexApi api, ISettingsService settings) + { + _api = api; + _customizationSettingsService = settings; + } + + private readonly IPlexApi _api; + private readonly ISettingsService _customizationSettingsService; + + public async Task RequestPin() + { + var pin = await _api.CreatePin(); + return pin; + } + + public async Task GetAccessTokenFromPin(int pinId) + { + var pin = await _api.GetPin(pinId); + if (pin.expiresAt < DateTime.UtcNow) + { + return string.Empty; + } + + if (pin.authToken.IsNullOrEmpty()) + { + // Looks like we do not have a pin yet, we should retry a few times. + var retryCount = 0; + var retryMax = 5; + var retryWaitMs = 1000; + while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax) + { + retryCount++; + await Task.Delay(retryWaitMs); + pin = await _api.GetPin(pinId); + } + } + return pin.authToken; + } + + public async Task GetAccount(string accessToken) + { + return await _api.GetAccount(accessToken); + } + + public async Task GetOAuthUrl(int pinId, string code) + { + var settings = await _customizationSettingsService.GetSettingsAsync(); + if (settings.ApplicationUrl.IsNullOrEmpty()) + { + return null; + } + + var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl, false); + return url; + } + + public Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress) + { + var url = _api.GetOAuthUrl(pinId, code, websiteAddress, true); + return url; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs new file mode 100644 index 000000000..142d4162a --- /dev/null +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -0,0 +1,16 @@ +using System; +using System.Threading.Tasks; +using Ombi.Api.Plex.Models; +using Ombi.Api.Plex.Models.OAuth; + +namespace Ombi.Core.Authentication +{ + public interface IPlexOAuthManager + { + Task GetAccessTokenFromPin(int pinId); + Task RequestPin(); + Task GetOAuthUrl(int pinId, string code); + Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress); + Task GetAccount(string accessToken); + } +} \ No newline at end of file diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index e2f667465..68f4b7218 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -51,6 +51,7 @@ using Ombi.Store.Repository.Requests; using Ombi.Updater; using PlexContentCacher = Ombi.Schedule.Jobs.Plex; using Ombi.Api.Telegram; +using Ombi.Core.Authentication; using Ombi.Core.Processor; using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.SickRage; @@ -82,6 +83,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterHttp(this IServiceCollection services) { diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index 3fcde951a..dd92eba18 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -6,8 +6,8 @@ namespace Ombi.Core.Settings.Models.External public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } + public string UniqueInstallCode { get; set; } public List Servers { get; set; } - } public class PlexServers : ExternalSettings diff --git a/src/Ombi/ClientApp/app/auth/auth.service.ts b/src/Ombi/ClientApp/app/auth/auth.service.ts index b9899c9a4..92b41ccd9 100644 --- a/src/Ombi/ClientApp/app/auth/auth.service.ts +++ b/src/Ombi/ClientApp/app/auth/auth.service.ts @@ -18,6 +18,10 @@ export class AuthService extends ServiceHelpers { return this.http.post(`${this.url}/`, JSON.stringify(login), {headers: this.headers}); } + public oAuth(pin: number): Observable { + return this.http.get(`${this.url}/${pin}`, {headers: this.headers}); + } + public requiresPassword(login: IUserLogin): Observable { return this.http.post(`${this.url}/requirePassword`, JSON.stringify(login), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/interfaces/IPlex.ts b/src/Ombi/ClientApp/app/interfaces/IPlex.ts index 7125ae4bc..823b80d32 100644 --- a/src/Ombi/ClientApp/app/interfaces/IPlex.ts +++ b/src/Ombi/ClientApp/app/interfaces/IPlex.ts @@ -2,6 +2,10 @@ user: IPlexUser; } +export interface IPlexOAuthAccessToken { + accessToken: string; +} + export interface IPlexUser { email: string; uuid: string; diff --git a/src/Ombi/ClientApp/app/services/applications/index.ts b/src/Ombi/ClientApp/app/services/applications/index.ts index 9433dfce0..98c61cf04 100644 --- a/src/Ombi/ClientApp/app/services/applications/index.ts +++ b/src/Ombi/ClientApp/app/services/applications/index.ts @@ -4,3 +4,4 @@ export * from "./plex.service"; export * from "./radarr.service"; export * from "./sonarr.service"; export * from "./tester.service"; +export * from "./plexoauth.service"; diff --git a/src/Ombi/ClientApp/app/services/applications/plex.service.ts b/src/Ombi/ClientApp/app/services/applications/plex.service.ts index c04a990e1..53fd31f9d 100644 --- a/src/Ombi/ClientApp/app/services/applications/plex.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plex.service.ts @@ -29,4 +29,8 @@ export class PlexService extends ServiceHelpers { public getFriends(): Observable { return this.http.get(`${this.url}Friends`, {headers: this.headers}); } + + public oAuth(wizard: boolean): Observable { + return this.http.get(`${this.url}oauth/${wizard}`, {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts b/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts new file mode 100644 index 000000000..59a884714 --- /dev/null +++ b/src/Ombi/ClientApp/app/services/applications/plexoauth.service.ts @@ -0,0 +1,20 @@ +import { PlatformLocation } from "@angular/common"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; + +import { Observable } from "rxjs/Rx"; + +import { ServiceHelpers } from "../service.helpers"; + +import { IPlexOAuthAccessToken } from "../../interfaces"; + +@Injectable() +export class PlexOAuthService extends ServiceHelpers { + constructor(http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/PlexOAuth/", platformLocation); + } + + public oAuth(pin: number): Observable { + return this.http.get(`${this.url}${pin}`, {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html index 2ba63c43e..6c4b846a7 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html @@ -20,6 +20,13 @@
+ +
+
+ +
+
+ diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts index 146d794fe..6bc21d628 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts @@ -70,4 +70,12 @@ export class PlexComponent { }); }); } + + public oauth() { + this.plexService.oAuth(true).subscribe(x => { + if(x.url) { + window.location.href = x.url; + } + }); + } } diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html new file mode 100644 index 000000000..d60f1d1f0 --- /dev/null +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.html @@ -0,0 +1,14 @@ + + +
+
+
+

Plex Authentication

+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts new file mode 100644 index 000000000..1397fe7f5 --- /dev/null +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts @@ -0,0 +1,76 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +import { ConfirmationService } from "primeng/primeng"; + +import { PlexOAuthService, IdentityService, SettingsService } from "../../services"; +import { AuthService } from "./../../auth/auth.service"; + +@Component({ + templateUrl: "./plexoauth.component.html", +}) +export class PlexOAuthComponent implements OnInit { + public pinId: number; + + constructor(private route: ActivatedRoute, + private plexOauth: PlexOAuthService, + private confirmationService: ConfirmationService, + private identityService: IdentityService, + private settings: SettingsService, + private router: Router, + private auth: AuthService) { + + this.route.params + .subscribe((params: any) => { + this.pinId = params.pin; + }); + } + + ngOnInit(): void { + this.plexOauth.oAuth(this.pinId).subscribe(x => { + x.accessToken; + + this.confirmationService.confirm({ + message: "Do you want your Plex user to be the main admin account on Ombi?", + header: "Use Plex Account", + icon: "fa fa-check", + accept: () => { + this.identityService.createWizardUser({ + username: "", + password: "", + usePlexAdminAccount: true, + }).subscribe(x => { + if (x) { + this.auth.oAuth(this.pinId).subscribe(c => { + localStorage.setItem("id_token", c.access_token); + + // Mark that we have done the settings now + this.settings.getOmbi().subscribe(ombi => { + ombi.wizard = true; + + this.settings.saveOmbi(ombi).subscribe(x => { + this.settings.getUserManagementSettings().subscribe(usr => { + + usr.importPlexAdmin = true; + this.settings.saveUserManagementSettings(usr).subscribe(saved => { + this.router.navigate(["login"]); + }); + }); + + }); + }); + }); + } else { + //this.notificationService.error("Could not get the Plex Admin Information"); + return; + } + }); + }, + reject: () => { + this.router.navigate(["Wizard/CreateAdmin"]); + }, + }); + }); + } + +} diff --git a/src/Ombi/ClientApp/app/wizard/wizard.module.ts b/src/Ombi/ClientApp/app/wizard/wizard.module.ts index 96cbdddc1..7eae2e4f4 100644 --- a/src/Ombi/ClientApp/app/wizard/wizard.module.ts +++ b/src/Ombi/ClientApp/app/wizard/wizard.module.ts @@ -14,6 +14,8 @@ import { WelcomeComponent } from "./welcome/welcome.component"; import { EmbyService } from "../services"; import { PlexService } from "../services"; import { IdentityService } from "../services"; +import { PlexOAuthService } from "../services"; +import { PlexOAuthComponent } from "./plex/plexoauth.component"; const routes: Routes = [ { path: "", component: WelcomeComponent}, @@ -21,6 +23,7 @@ const routes: Routes = [ { path: "Plex", component: PlexComponent}, { path: "Emby", component: EmbyComponent}, { path: "CreateAdmin", component: CreateAdminComponent}, + { path: "OAuth/:pin", component: PlexOAuthComponent}, ]; @NgModule({ imports: [ @@ -33,6 +36,7 @@ const routes: Routes = [ WelcomeComponent, MediaServerComponent, PlexComponent, + PlexOAuthComponent, CreateAdminComponent, EmbyComponent, ], @@ -44,6 +48,7 @@ const routes: Routes = [ IdentityService, EmbyService, ConfirmationService, + PlexOAuthService, ], }) diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 2d45d7565..cde78bc64 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Ombi.Api.Plex; using Ombi.Api.Plex.Models; using Ombi.Attributes; +using Ombi.Core.Authentication; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; @@ -21,16 +22,18 @@ namespace Ombi.Controllers.External public class PlexController : Controller { public PlexController(IPlexApi plexApi, ISettingsService plexSettings, - ILogger logger) + ILogger logger, IPlexOAuthManager manager) { PlexApi = plexApi; PlexSettings = plexSettings; _log = logger; + _plexOAuthManager = manager; } private IPlexApi PlexApi { get; } private ISettingsService PlexSettings { get; } private readonly ILogger _log; + private readonly IPlexOAuthManager _plexOAuthManager; /// /// Signs into the Plex API. @@ -66,6 +69,7 @@ namespace Ombi.Controllers.External _log.LogDebug("Adding first server"); settings.Enable = true; + settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); settings.Servers = new List { new PlexServers { @@ -173,5 +177,37 @@ namespace Ombi.Controllers.External // Filter out any dupes return vm.DistinctBy(x => x.Id); } + + [HttpGet("oauth/{wizard:bool}")] + [AllowAnonymous] + public async Task OAuth(bool wizard) + { + //https://app.plex.tv/auth#?forwardUrl=http://google.com/&clientID=Ombi-Test&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO&pinID=798798&code=4lgfd + // Plex OAuth + // Redirect them to Plex + // We need a PIN first + var pin = await _plexOAuthManager.RequestPin(); + + Uri url; + if (!wizard) + { + url = await _plexOAuthManager.GetOAuthUrl(pin.id, pin.code); + } + else + { + var websiteAddress =$"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}"; + url = _plexOAuthManager.GetWizardOAuthUrl(pin.id, pin.code, websiteAddress); + } + + if (url == null) + { + return new JsonResult(new + { + error = "Application URL has not been set" + }); + } + + return new JsonResult(new {url = url.ToString()}); + } } } diff --git a/src/Ombi/Controllers/PlexOAuthController.cs b/src/Ombi/Controllers/PlexOAuthController.cs index 8f33e37be..2aad2a2a9 100644 --- a/src/Ombi/Controllers/PlexOAuthController.cs +++ b/src/Ombi/Controllers/PlexOAuthController.cs @@ -1,9 +1,19 @@ -using System.Net; +using System; +using System.Collections.Generic; +using System.Net; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http.Internal; using System.IO; +using System.Linq; using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Ombi.Api.Plex; +using Ombi.Core.Authentication; +using Ombi.Core.Settings; +using Ombi.Core.Settings.Models.External; +using Ombi.Helpers; namespace Ombi.Controllers { @@ -12,29 +22,57 @@ namespace Ombi.Controllers [AllowAnonymous] public class PlexOAuthController : Controller { - - [HttpGet] - public IActionResult OAuthCallBack() + public PlexOAuthController(IPlexOAuthManager manager, IPlexApi plexApi, ISettingsService plexSettings, + ILogger log) { - var bodyStr = ""; - var req = Request; + _manager = manager; + _plexApi = plexApi; + _plexSettings = plexSettings; + _log = log; + } - // Allows using several time the stream in ASP.Net Core - req.EnableRewind(); + private readonly IPlexOAuthManager _manager; + private readonly IPlexApi _plexApi; + private readonly ISettingsService _plexSettings; + private readonly ILogger _log; - // Arguments: Stream, Encoding, detect encoding, buffer size - // AND, the most important: keep stream opened - using (StreamReader reader - = new StreamReader(req.Body, Encoding.UTF8, true, 1024, true)) + [HttpGet("{pinId:int}")] + public async Task OAuthWizardCallBack([FromRoute] int pinId) + { + var accessToken = await _manager.GetAccessTokenFromPin(pinId); + if (accessToken.IsNullOrEmpty()) + { + return Json(new + { + success = false, + error = "Authentication did not work. Please try again" + }); + } + var settings = await _plexSettings.GetSettingsAsync(); + var server = await _plexApi.GetServer(accessToken); + var servers = server.Server.FirstOrDefault(); + if (servers == null) { - bodyStr = reader.ReadToEnd(); + _log.LogWarning("Looks like we can't find any Plex Servers"); } + _log.LogDebug("Adding first server"); - // Rewind, so the core is not lost when it looks the body for the request - req.Body.Position = 0; + settings.Enable = true; + settings.Servers = new List { + new PlexServers + { + PlexAuthToken = accessToken, + Id = new Random().Next(), + Ip = servers?.LocalAddresses?.Split(new []{','}, StringSplitOptions.RemoveEmptyEntries)?.FirstOrDefault() ?? string.Empty, + MachineIdentifier = servers?.MachineIdentifier ?? string.Empty, + Port = int.Parse(servers?.Port ?? "0"), + Ssl = (servers?.Scheme ?? "http") != "http", + Name = "Server 1", + } + }; - // Do your work with bodyStr - return Ok(); + await _plexSettings.SaveSettingsAsync(settings); + return Json(new { accessToken }); } } } diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 30469cd57..867a7517c 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -142,7 +142,13 @@ namespace Ombi.Controllers [HttpGet("plex")] public async Task PlexSettings() { - return await Get(); + var s = await Get(); + if (s.UniqueInstallCode.IsNullOrEmpty()) + { + s.UniqueInstallCode = Guid.NewGuid().ToString("N"); + } + + return s; } /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 5d524b7ba..62e43635d 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -25,18 +25,21 @@ namespace Ombi.Controllers [Produces("application/json")] public class TokenController { - public TokenController(OmbiUserManager um, IOptions ta, IAuditRepository audit, ITokenRepository token) + public TokenController(OmbiUserManager um, IOptions ta, IAuditRepository audit, ITokenRepository token, + IPlexOAuthManager oAuthManager) { _userManager = um; _tokenAuthenticationOptions = ta.Value; _audit = audit; _token = token; + _plexOAuthManager = oAuthManager; } private readonly TokenAuthentication _tokenAuthenticationOptions; private readonly IAuditRepository _audit; private readonly ITokenRepository _token; private readonly OmbiUserManager _userManager; + private readonly IPlexOAuthManager _plexOAuthManager; /// /// Gets the token. @@ -69,63 +72,99 @@ namespace Ombi.Controllers // Verify Password if (await _userManager.CheckPasswordAsync(user, model.Password)) { - var roles = await _userManager.GetRolesAsync(user); - - if (roles.Contains(OmbiRoles.Disabled)) - { - return new UnauthorizedResult(); - } - - user.LastLoggedIn = DateTime.UtcNow; - await _userManager.UpdateAsync(user); - - var claims = new List - { - new Claim(JwtRegisteredClaimNames.Sub, user.UserName), - new Claim(ClaimTypes.NameIdentifier, user.Id), - new Claim(ClaimTypes.Name, user.UserName), - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) - }; - claims.AddRange(roles.Select(role => new Claim("role", role))); - - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); - var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - - - var token = new JwtSecurityToken( - claims: claims, - expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), - signingCredentials: creds, - audience: "Ombi", issuer: "Ombi" - ); - var accessToken = new JwtSecurityTokenHandler().WriteToken(token); - if (model.RememberMe) - { - // Save the token so we can refresh it later - //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); - } + return await CreateToken(model.RememberMe, user); + } + } + else + { + // Plex OAuth + // Redirect them to Plex + // We need a PIN first + var pin = await _plexOAuthManager.RequestPin(); + //https://app.plex.tv/auth#?forwardUrl=http://google.com/&clientID=Ombi-Test&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO&pinID=798798&code=4lgfd + var url = await _plexOAuthManager.GetOAuthUrl(pin.id, pin.code); + if (url == null) + { return new JsonResult(new { - access_token = accessToken, - expiration = token.ValidTo + error = "Application URL has not been set" }); } + return new RedirectResult(url.ToString()); } - else + + return new UnauthorizedResult(); + } + + private async Task CreateToken(bool rememberMe, OmbiUser user) + { + var roles = await _userManager.GetRolesAsync(user); + + if (roles.Contains(OmbiRoles.Disabled)) { - // Plex OAuth - // Redirect them to Plex + return new UnauthorizedResult(); + } - var request = new Request("auth", "https://app.plex.tv", HttpMethod.Get); - request.AddQueryString("clientID", "OMBIv3"); - request.AddQueryString("forwardUrl", "http://localhost:5000"); - request.AddQueryString("context-device-product", "http://localhost:5000"); - return new RedirectResult("https://app.plex.tv/auth#?forwardUrl=http://localhost:5000/api/v1/plexoauth&clientID=OMBIv3&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO"); + user.LastLoggedIn = DateTime.UtcNow; + await _userManager.UpdateAsync(user); + var claims = new List + { + new Claim(JwtRegisteredClaimNames.Sub, user.UserName), + new Claim(ClaimTypes.NameIdentifier, user.Id), + new Claim(ClaimTypes.Name, user.UserName), + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) + }; + claims.AddRange(roles.Select(role => new Claim("role", role))); + + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + + var token = new JwtSecurityToken( + claims: claims, + expires: rememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), + signingCredentials: creds, + audience: "Ombi", issuer: "Ombi" + ); + var accessToken = new JwtSecurityTokenHandler().WriteToken(token); + if (rememberMe) + { + // Save the token so we can refresh it later + //await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); } - return new UnauthorizedResult(); + return new JsonResult(new + { + access_token = accessToken, + expiration = token.ValidTo + }); + } + + [HttpGet("{pinId:int}")] + public async Task OAuth(int pinId) + { + var accessToken = await _plexOAuthManager.GetAccessTokenFromPin(pinId); + + // Let's look for the users account + var account = await _plexOAuthManager.GetAccount(accessToken); + + // Get the ombi user + var user = await _userManager.FindByNameAsync(account.user.username); + + if (user == null) + { + // Could this be an email login? + user = await _userManager.FindByEmailAsync(account.user.email); + + if (user == null) + { + return new UnauthorizedResult(); + } + } + + return await CreateToken(true, user); } /// From 1c54eedc6ff66ac93ecdc64da7cb7612c42485d6 Mon Sep 17 00:00:00 2001 From: Anojh Thayaparan Date: Thu, 19 Apr 2018 11:08:49 -0700 Subject: [PATCH 036/495] Added View on Emby Button (#2173) * Added Emby content url property * Fetch and set the Emby url property * Added View on Emby button to frontend and did UI work * removed debug logging --- .../Models/Search/SearchViewModel.cs | 1 + .../Rule/Rules/Search/EmbyAvailabilityRule.cs | 1 + src/Ombi.Helpers/EmbyHelper.cs | 17 + .../Jobs/Emby/EmbyContentSync.cs | 2 + src/Ombi.Store/Entities/EmbyContent.cs | 1 + .../20180419054711_EmbyButton.Designer.cs | 950 ++++++++++++++++++ .../Migrations/20180419054711_EmbyButton.cs | 24 + .../Migrations/OmbiContextModelSnapshot.cs | 2 + .../app/interfaces/ISearchMovieResult.ts | 1 + .../app/interfaces/ISearchTvResult.ts | 1 + .../app/search/moviesearch.component.html | 1 + .../app/search/tvsearch.component.html | 24 +- src/Ombi/wwwroot/translations/da.json | 1 + src/Ombi/wwwroot/translations/de.json | 1 + src/Ombi/wwwroot/translations/en.json | 1 + src/Ombi/wwwroot/translations/es.json | 1 + src/Ombi/wwwroot/translations/fr.json | 1 + src/Ombi/wwwroot/translations/it.json | 1 + src/Ombi/wwwroot/translations/nl.json | 1 + src/Ombi/wwwroot/translations/no.json | 1 + src/Ombi/wwwroot/translations/sv.json | 1 + 21 files changed, 1026 insertions(+), 8 deletions(-) create mode 100644 src/Ombi.Helpers/EmbyHelper.cs create mode 100644 src/Ombi.Store/Migrations/20180419054711_EmbyButton.Designer.cs create mode 100644 src/Ombi.Store/Migrations/20180419054711_EmbyButton.cs diff --git a/src/Ombi.Core/Models/Search/SearchViewModel.cs b/src/Ombi.Core/Models/Search/SearchViewModel.cs index 8c8d49aff..0de295a69 100644 --- a/src/Ombi.Core/Models/Search/SearchViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchViewModel.cs @@ -10,6 +10,7 @@ namespace Ombi.Core.Models.Search public bool Requested { get; set; } public bool Available { get; set; } public string PlexUrl { get; set; } + public string EmbyUrl { get; set; } public string Quality { get; set; } public abstract RequestType Type { get; } diff --git a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs index 8ac96701d..4f2f56b28 100644 --- a/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/EmbyAvailabilityRule.cs @@ -44,6 +44,7 @@ namespace Ombi.Core.Rule.Rules.Search if (item != null) { obj.Available = true; + obj.EmbyUrl = item.Url; if (obj.Type == RequestType.TvShow) { diff --git a/src/Ombi.Helpers/EmbyHelper.cs b/src/Ombi.Helpers/EmbyHelper.cs new file mode 100644 index 000000000..cf6904a0c --- /dev/null +++ b/src/Ombi.Helpers/EmbyHelper.cs @@ -0,0 +1,17 @@ +using System; +using System.Globalization; +using System.Collections.Generic; +using System.Text; + +namespace Ombi.Helpers +{ + public class EmbyHelper + { + public static string GetEmbyMediaUrl(string mediaId) + { + var url = + $"http://app.emby.media/itemdetails.html?id={mediaId}"; + return url; + } + } +} diff --git a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs index 99d4f5a85..4d0026f48 100644 --- a/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Emby/EmbyContentSync.cs @@ -114,6 +114,7 @@ namespace Ombi.Schedule.Jobs.Emby Title = tvInfo.Name, Type = EmbyMediaType.Series, EmbyId = tvShow.Id, + Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id), AddedAt = DateTime.UtcNow }); } @@ -135,6 +136,7 @@ namespace Ombi.Schedule.Jobs.Emby Title = movieInfo.Name, Type = EmbyMediaType.Movie, EmbyId = movieInfo.Id, + Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id), AddedAt = DateTime.UtcNow, }); } diff --git a/src/Ombi.Store/Entities/EmbyContent.cs b/src/Ombi.Store/Entities/EmbyContent.cs index 1d3f57f13..4506ef071 100644 --- a/src/Ombi.Store/Entities/EmbyContent.cs +++ b/src/Ombi.Store/Entities/EmbyContent.cs @@ -48,6 +48,7 @@ namespace Ombi.Store.Entities public string TheMovieDbId { get; set; } public string TvDbId { get; set; } + public string Url { get; set; } public ICollection Episodes { get; set; } } diff --git a/src/Ombi.Store/Migrations/20180419054711_EmbyButton.Designer.cs b/src/Ombi.Store/Migrations/20180419054711_EmbyButton.Designer.cs new file mode 100644 index 000000000..c5ddc8fa2 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180419054711_EmbyButton.Designer.cs @@ -0,0 +1,950 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180419054711_EmbyButton")] + partial class EmbyButton + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + 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.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + 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.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + 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.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + 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.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180419054711_EmbyButton.cs b/src/Ombi.Store/Migrations/20180419054711_EmbyButton.cs new file mode 100644 index 000000000..07e5cb730 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180419054711_EmbyButton.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class EmbyButton : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Url", + table: "EmbyContent", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Url", + table: "EmbyContent"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index b722b0d30..248551d97 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -197,6 +197,8 @@ namespace Ombi.Store.Migrations b.Property("Type"); + b.Property("Url"); + b.HasKey("Id"); b.ToTable("EmbyContent"); diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts index c5301d2b0..ee66598fd 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISearchMovieResult.ts @@ -21,6 +21,7 @@ requested: boolean; available: boolean; plexUrl: string; + embyUrl: string; quality: string; digitalReleaseDate: Date; diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts index f0afa76b2..81d716bfc 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISearchTvResult.ts @@ -27,6 +27,7 @@ export interface ISearchTvResult { requested: boolean; available: boolean; plexUrl: string; + embyUrl: string; firstSeason: boolean; latestSeason: boolean; } diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index cac78531b..3130cb79d 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -85,6 +85,7 @@

+ + {{ 'Search.ViewOnPlex' | translate }} + + +
-
-
+
+
diff --git a/src/Ombi/wwwroot/translations/da.json b/src/Ombi/wwwroot/translations/da.json index ec4b0bce5..dd3872ac0 100644 --- a/src/Ombi/wwwroot/translations/da.json +++ b/src/Ombi/wwwroot/translations/da.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Se på Plex", + "ViewOnEmby": "Se på Emby", "RequestAdded": "{{title}} er anmodet med succes", "Similar": "Similar", "Movies": { diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index df8b6a724..2a46e2db3 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "In Plex anschauen", + "ViewOnEmby": "In Emby anschauen", "RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt", "Similar": "Similar", "Movies": { diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 124c26fc5..cb7c0fe8b 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -81,6 +81,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease":"Theatrical Release: {{date}}", "ViewOnPlex": "View On Plex", + "ViewOnEmby": "View On Emby", "RequestAdded": "Request for {{title}} has been added successfully", "Similar":"Similar", "Movies": { diff --git a/src/Ombi/wwwroot/translations/es.json b/src/Ombi/wwwroot/translations/es.json index 6b2f87874..17036ade3 100644 --- a/src/Ombi/wwwroot/translations/es.json +++ b/src/Ombi/wwwroot/translations/es.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Ver en Plex", + "ViewOnEmby": "Ver en Emby", "RequestAdded": "La solicitud de {{title}} se ha agregado con éxito", "Similar": "Similar", "Movies": { diff --git a/src/Ombi/wwwroot/translations/fr.json b/src/Ombi/wwwroot/translations/fr.json index 983763a08..bc3f395dd 100644 --- a/src/Ombi/wwwroot/translations/fr.json +++ b/src/Ombi/wwwroot/translations/fr.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Regarder sur Plex", + "ViewOnEmby": "Regarder sur Emby", "RequestAdded": "La demande pour {{title}} a été ajoutée avec succès", "Similar": "Similar", "Movies": { diff --git a/src/Ombi/wwwroot/translations/it.json b/src/Ombi/wwwroot/translations/it.json index 2081ce2a1..1e91047ff 100644 --- a/src/Ombi/wwwroot/translations/it.json +++ b/src/Ombi/wwwroot/translations/it.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Guarda su Plex", + "ViewOnEmby": "Guarda su Emby", "RequestAdded": "La richiesta per {{title}} è stata aggiunta correttamente", "Similar": "Similar", "Movies": { diff --git a/src/Ombi/wwwroot/translations/nl.json b/src/Ombi/wwwroot/translations/nl.json index a9ba6b683..d5fd62bba 100644 --- a/src/Ombi/wwwroot/translations/nl.json +++ b/src/Ombi/wwwroot/translations/nl.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Bekijk op Plex", + "ViewOnEmby": "Bekijk op Emby", "RequestAdded": "Aanvraag voor {{title}} is succesvol toegevoegd", "Similar": "Similar", "Movies": { diff --git a/src/Ombi/wwwroot/translations/no.json b/src/Ombi/wwwroot/translations/no.json index cbaf1379a..d94e977df 100644 --- a/src/Ombi/wwwroot/translations/no.json +++ b/src/Ombi/wwwroot/translations/no.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital utgivelse: {{date}}", "TheatricalRelease": "Kinopremiere: {{date}}", "ViewOnPlex": "Spill av på Plex", + "ViewOnEmby": "Spill av på Emby", "RequestAdded": "Forespørsel om {{title}} er lagt til", "Similar": "Lignende", "Movies": { diff --git a/src/Ombi/wwwroot/translations/sv.json b/src/Ombi/wwwroot/translations/sv.json index b750b5e0c..cbc87c2d4 100644 --- a/src/Ombi/wwwroot/translations/sv.json +++ b/src/Ombi/wwwroot/translations/sv.json @@ -76,6 +76,7 @@ "DigitalDate": "Digital Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}", "ViewOnPlex": "Visa på Plex", + "ViewOnEmby": "Visa på Emby", "RequestAdded": "Efterfrågan om {{title}} har lagts till", "Similar": "Similar", "Movies": { From 2cd9c4d9b123f4849c11304d5fd2e159553f32c7 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Thu, 19 Apr 2018 22:15:20 +0100 Subject: [PATCH 037/495] frontend is all done !wip --- src/Ombi.Api.Plex/PlexApi.cs | 2 +- src/Ombi/ClientApp/app/app.module.ts | 3 ++ src/Ombi/ClientApp/app/auth/IUserLogin.ts | 1 + .../ClientApp/app/login/login.component.html | 12 ++++++ .../ClientApp/app/login/login.component.ts | 39 ++++++++++++++++++- .../app/login/loginoauth.component.html | 9 +++++ .../app/login/loginoauth.component.ts | 35 +++++++++++++++++ .../app/services/settings.service.ts | 4 ++ .../createadmin/createadmin.component.ts | 2 +- .../app/wizard/plex/plex.component.html | 2 +- .../app/wizard/plex/plex.component.ts | 24 ++++-------- .../app/wizard/plex/plexoauth.component.ts | 26 +++++-------- src/Ombi/Controllers/SettingsController.cs | 10 +++++ src/Ombi/Controllers/TokenController.cs | 29 +++++++------- 14 files changed, 146 insertions(+), 52 deletions(-) create mode 100644 src/Ombi/ClientApp/app/login/loginoauth.component.html create mode 100644 src/Ombi/ClientApp/app/login/loginoauth.component.ts diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 5fb87aca7..6d814adf3 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -208,7 +208,7 @@ namespace Ombi.Api.Plex AddHeaders(request); var forwardUrl = wizard ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) - : new Request($"api/v1/PlexOAuth/{pinId}", applicationUrl, HttpMethod.Get); + : new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get); request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); request.AddQueryString("pinID", pinId.ToString()); diff --git a/src/Ombi/ClientApp/app/app.module.ts b/src/Ombi/ClientApp/app/app.module.ts index 0936b13cd..1421b1117 100644 --- a/src/Ombi/ClientApp/app/app.module.ts +++ b/src/Ombi/ClientApp/app/app.module.ts @@ -24,6 +24,7 @@ import { CookieComponent } from "./auth/cookie.component"; import { PageNotFoundComponent } from "./errors/not-found.component"; import { LandingPageComponent } from "./landingpage/landingpage.component"; import { LoginComponent } from "./login/login.component"; +import { LoginOAuthComponent } from "./login/loginoauth.component"; import { ResetPasswordComponent } from "./login/resetpassword.component"; import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component"; @@ -41,6 +42,7 @@ const routes: Routes = [ { path: "*", component: PageNotFoundComponent }, { path: "", redirectTo: "/search", pathMatch: "full" }, { path: "login", component: LoginComponent }, + { path: "Login/OAuth/:pin", component: LoginOAuthComponent }, { path: "login/:landing", component: LoginComponent }, { path: "reset", component: ResetPasswordComponent }, { path: "token", component: TokenResetPasswordComponent }, @@ -116,6 +118,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo ResetPasswordComponent, TokenResetPasswordComponent, CookieComponent, + LoginOAuthComponent, ], providers: [ NotificationService, diff --git a/src/Ombi/ClientApp/app/auth/IUserLogin.ts b/src/Ombi/ClientApp/app/auth/IUserLogin.ts index af152a6b1..609055c8e 100644 --- a/src/Ombi/ClientApp/app/auth/IUserLogin.ts +++ b/src/Ombi/ClientApp/app/auth/IUserLogin.ts @@ -2,6 +2,7 @@ username: string; password: string; rememberMe: boolean; + usePlexOAuth: boolean; } export interface ILocalUser { diff --git a/src/Ombi/ClientApp/app/login/login.component.html b/src/Ombi/ClientApp/app/login/login.component.html index 5fc0150ff..528fe9917 100644 --- a/src/Ombi/ClientApp/app/login/login.component.html +++ b/src/Ombi/ClientApp/app/login/login.component.html @@ -7,11 +7,13 @@ include the remember me checkbox
+

+
+
+ + + diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 661850e86..cde2ef085 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -25,9 +25,39 @@ export class LoginComponent implements OnDestroy, OnInit { public form: FormGroup; public customizationSettings: ICustomizationSettings; public authenticationSettings: IAuthenticationSettings; + public plexEnabled: boolean; public background: any; public landingFlag: boolean; public baseUrl: string; + + public get showLoginForm(): boolean { + if(this.customizationSettings.applicationUrl && this.plexEnabled) { + this.loginWithOmbi = false; + return false; + } + if(!this.customizationSettings.applicationUrl || !this.plexEnabled) { + + this.loginWithOmbi = true; + return true; + } + if(this.loginWithOmbi) { + return true; + } + + + this.loginWithOmbi = true; + return true; + } + public loginWithOmbi: boolean = false; + + public get appName(): string { + if(this.customizationSettings.applicationName) { + return this.customizationSettings.applicationName; + } else { + return "Ombi"; + } + } + private timer: any; private errorBody: string; @@ -68,6 +98,7 @@ export class LoginComponent implements OnDestroy, OnInit { public ngOnInit() { this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x); this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); + this.settingsService.getStatusPlex().subscribe(x => this.plexEnabled = x); this.images.getRandomBackground().subscribe(x => { this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")"); }); @@ -90,7 +121,7 @@ export class LoginComponent implements OnDestroy, OnInit { return; } const value = form.value; - const user = { password: value.password, username: value.username, rememberMe:value.rememberMe }; + const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false }; this.authService.requiresPassword(user).subscribe(x => { if(x && this.authenticationSettings.allowNoPassword) { // Looks like this user requires a password @@ -111,6 +142,12 @@ export class LoginComponent implements OnDestroy, OnInit { }); } + public oauth() { + this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => { + window.location.href = x.url; + }) + } + public ngOnDestroy() { clearInterval(this.timer); } diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.html b/src/Ombi/ClientApp/app/login/loginoauth.component.html new file mode 100644 index 000000000..e80f1239f --- /dev/null +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.html @@ -0,0 +1,9 @@ + +
+ +
+
+ +
+
+
diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.ts b/src/Ombi/ClientApp/app/login/loginoauth.component.ts new file mode 100644 index 000000000..a0a9a9414 --- /dev/null +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.ts @@ -0,0 +1,35 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +import { AuthService } from "../auth/auth.service"; + +@Component({ + templateUrl: "./loginoauth.component.html" +}) +export class LoginOAuthComponent implements OnInit { + public pin: number; + + constructor(private authService: AuthService, private router: Router, + private route: ActivatedRoute) { + this.route.params + .subscribe((params: any) => { + this.pin = params.pin; + + }); + } + + ngOnInit(): void { + this.auth(); + } + + public auth() { + this.authService.oAuth(this.pin).subscribe(x => { + localStorage.setItem("id_token", x.access_token); + + if (this.authService.loggedIn()) { + this.router.navigate(["search"]); + } + + }); + } +} diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index f6f770a19..61a85874e 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -71,6 +71,10 @@ export class SettingsService extends ServiceHelpers { return this.http.get(`${this.url}/Plex/`, {headers: this.headers}); } + public getStatusPlex(): Observable { + return this.http.get(`${this.url}/Plexstatus/`, {headers: this.headers}); + } + public savePlex(settings: IPlexSettings): Observable { return this.http.post(`${this.url}/Plex/`, JSON.stringify(settings), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts b/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts index 481c96a99..07f47f265 100644 --- a/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts +++ b/src/Ombi/ClientApp/app/wizard/createadmin/createadmin.component.ts @@ -21,7 +21,7 @@ export class CreateAdminComponent { this.identityService.createWizardUser({username: this.username, password: this.password, usePlexAdminAccount: false}).subscribe(x => { if (x) { // Log me in. - this.auth.login({ username: this.username, password: this.password, rememberMe:false }).subscribe(c => { + this.auth.login({ username: this.username, password: this.password, rememberMe: false, usePlexOAuth:false }).subscribe(c => { localStorage.setItem("id_token", c.access_token); diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html index 6c4b846a7..5d69c435b 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html @@ -23,7 +23,7 @@
- +
diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts index 6bc21d628..2f5b2dd79 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts @@ -1,7 +1,6 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; -import { ConfirmationService } from "primeng/primeng"; import { PlexService } from "../../services"; import { IdentityService, NotificationService, SettingsService } from "../../services"; @@ -17,7 +16,6 @@ export class PlexComponent { constructor(private plexService: PlexService, private router: Router, private notificationService: NotificationService, - private confirmationService: ConfirmationService, private identityService: IdentityService, private settings: SettingsService, private auth: AuthService) { } @@ -28,25 +26,21 @@ export class PlexComponent { this.notificationService.error("Username or password was incorrect. Could not authenticate with Plex."); return; } - this.confirmationService.confirm({ - message: "Do you want your Plex user to be the main admin account on Ombi?", - header: "Use Plex Account", - icon: "fa fa-check", - accept: () => { + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, - }).subscribe(x => { - if (x) { - this.auth.login({ username: this.login, password: this.password, rememberMe:false }).subscribe(c => { + }).subscribe(y => { + if (y) { + this.auth.login({ username: this.login, password: this.password, rememberMe: false, usePlexOAuth: false }).subscribe(c => { localStorage.setItem("id_token", c.access_token); // Mark that we have done the settings now this.settings.getOmbi().subscribe(ombi => { ombi.wizard = true; - this.settings.saveOmbi(ombi).subscribe(x => { + this.settings.saveOmbi(ombi).subscribe(s => { this.settings.getUserManagementSettings().subscribe(usr => { usr.importPlexAdmin = true; @@ -63,12 +57,8 @@ export class PlexComponent { return; } }); - }, - reject: () => { - this.router.navigate(["Wizard/CreateAdmin"]); - }, - }); - }); + } + ); } public oauth() { diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts index 1397fe7f5..7d0d0bf9d 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts @@ -1,8 +1,6 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { ConfirmationService } from "primeng/primeng"; - import { PlexOAuthService, IdentityService, SettingsService } from "../../services"; import { AuthService } from "./../../auth/auth.service"; @@ -14,7 +12,6 @@ export class PlexOAuthComponent implements OnInit { constructor(private route: ActivatedRoute, private plexOauth: PlexOAuthService, - private confirmationService: ConfirmationService, private identityService: IdentityService, private settings: SettingsService, private router: Router, @@ -28,19 +25,18 @@ export class PlexOAuthComponent implements OnInit { ngOnInit(): void { this.plexOauth.oAuth(this.pinId).subscribe(x => { - x.accessToken; + if(!x.accessToken) { + return; + // RETURN + } - this.confirmationService.confirm({ - message: "Do you want your Plex user to be the main admin account on Ombi?", - header: "Use Plex Account", - icon: "fa fa-check", - accept: () => { + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, - }).subscribe(x => { - if (x) { + }).subscribe(u => { + if (u) { this.auth.oAuth(this.pinId).subscribe(c => { localStorage.setItem("id_token", c.access_token); @@ -48,7 +44,7 @@ export class PlexOAuthComponent implements OnInit { this.settings.getOmbi().subscribe(ombi => { ombi.wizard = true; - this.settings.saveOmbi(ombi).subscribe(x => { + this.settings.saveOmbi(ombi).subscribe(s => { this.settings.getUserManagementSettings().subscribe(usr => { usr.importPlexAdmin = true; @@ -65,11 +61,7 @@ export class PlexOAuthComponent implements OnInit { return; } }); - }, - reject: () => { - this.router.navigate(["Wizard/CreateAdmin"]); - }, - }); + }); } diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 867a7517c..1f0d62147 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -151,6 +151,16 @@ namespace Ombi.Controllers return s; } + [HttpGet("plexstatus")] + [AllowAnonymous] + public async Task PlexStatusSettings() + { + var s = await Get(); + + + return s.Enable; + } + /// /// Save the Plex settings. /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 62e43635d..624267989 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -49,26 +49,27 @@ namespace Ombi.Controllers [HttpPost] public async Task GetToken([FromBody] UserAuthModel model) { - await _audit.Record(AuditType.None, AuditArea.Authentication, + if (!model.UsePlexOAuth) + { + await _audit.Record(AuditType.None, AuditArea.Authentication, $"UserName {model.Username} attempting to authenticate"); - var user = await _userManager.FindByNameAsync(model.Username); - - if (user == null) - { - // Could this be an email login? - user = await _userManager.FindByEmailAsync(model.Username); + var user = await _userManager.FindByNameAsync(model.Username); if (user == null) { - return new UnauthorizedResult(); + // Could this be an email login? + user = await _userManager.FindByEmailAsync(model.Username); + + if (user == null) + { + return new UnauthorizedResult(); + } + + user.EmailLogin = true; } - user.EmailLogin = true; - } - if (!model.UsePlexOAuth) - { // Verify Password if (await _userManager.CheckPasswordAsync(user, model.Password)) { @@ -91,7 +92,7 @@ namespace Ombi.Controllers error = "Application URL has not been set" }); } - return new RedirectResult(url.ToString()); + return new JsonResult(new { url = url.ToString() }); } return new UnauthorizedResult(); @@ -183,7 +184,7 @@ namespace Ombi.Controllers { return new UnauthorizedResult(); } - + throw new NotImplementedException(); } From 5531af462cc67189f6a5d37f1c388dd230ea1fac Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Thu, 19 Apr 2018 22:18:25 +0100 Subject: [PATCH 038/495] Made the app name customizable for the OAUth !wip --- src/Ombi.Api.Plex/PlexApi.cs | 34 +++++++++++-------- .../Settings/Models/External/PlexSettings.cs | 1 - 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 6d814adf3..a16dee9ec 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -10,38 +10,42 @@ using Ombi.Api.Plex.Models.Status; using Ombi.Core.Settings; using Ombi.Core.Settings.Models.External; using Ombi.Helpers; +using Ombi.Settings.Settings.Models; namespace Ombi.Api.Plex { public class PlexApi : IPlexApi { - public PlexApi(IApi api, ISettingsService settings) + public PlexApi(IApi api, ISettingsService settings) { Api = api; - _plex = settings; + _custom = settings; } private IApi Api { get; } - private readonly ISettingsService _plex; + private readonly ISettingsService _custom; - private string _clientId; - private string ClientIdSecret + private string _app; + private string ApplicationName { get { - if (string.IsNullOrEmpty(_clientId)) + if (string.IsNullOrEmpty(_app)) { - var settings = _plex.GetSettings(); - if (settings.UniqueInstallCode.IsNullOrEmpty()) + var settings = _custom.GetSettings(); + if (settings.ApplicationName.IsNullOrEmpty()) { - settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); - _plex.SaveSettings(settings); + _app = "Ombi"; + } + else + { + _app = settings.ApplicationName; } - _clientId = settings.UniqueInstallCode; + return _app; } - return _clientId; + return _app; } } @@ -215,7 +219,7 @@ namespace Ombi.Api.Plex request.AddQueryString("code", code); request.AddQueryString("context[device][product]", "Ombi"); request.AddQueryString("context[device][environment]", "bundled"); - request.AddQueryString("clientID", $"OmbiV3{ClientIdSecret}"); + request.AddQueryString("clientID", $"OmbiV3"); if (request.FullUri.Fragment.Equals("#")) { @@ -246,8 +250,8 @@ namespace Ombi.Api.Plex /// private void AddHeaders(Request request) { - request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3{ClientIdSecret}"); - request.AddHeader("X-Plex-Product", "Ombi"); + request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3"); + request.AddHeader("X-Plex-Product", ApplicationName); request.AddHeader("X-Plex-Version", "3"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); request.AddHeader("Accept", "application/json"); diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index dd92eba18..3faba3e42 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -6,7 +6,6 @@ namespace Ombi.Core.Settings.Models.External public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } - public string UniqueInstallCode { get; set; } public List Servers { get; set; } } From f05a9a5090ecc68ba56522c7ecf453e3dc97b549 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Fri, 20 Apr 2018 10:41:40 +0100 Subject: [PATCH 039/495] Removed no longer needed stuff and fixed linting !wip --- src/Ombi/ClientApp/app/login/login.component.ts | 4 +--- src/Ombi/ClientApp/app/login/loginoauth.component.ts | 4 ++-- src/Ombi/ClientApp/app/wizard/plex/plex.component.ts | 5 ++--- src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts | 7 +++---- src/Ombi/Controllers/External/PlexController.cs | 1 - src/Ombi/Controllers/SettingsController.cs | 4 ---- 6 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index cde2ef085..3c2636b2f 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -44,7 +44,6 @@ export class LoginComponent implements OnDestroy, OnInit { return true; } - this.loginWithOmbi = true; return true; } @@ -145,7 +144,7 @@ export class LoginComponent implements OnDestroy, OnInit { public oauth() { this.authService.login({usePlexOAuth: true, password:"",rememberMe:true,username:""}).subscribe(x => { window.location.href = x.url; - }) + }); } public ngOnDestroy() { @@ -161,5 +160,4 @@ export class LoginComponent implements OnDestroy, OnInit { .bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%), url(" + x.url + ")"); }); } - } diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.ts b/src/Ombi/ClientApp/app/login/loginoauth.component.ts index a0a9a9414..03922d8b3 100644 --- a/src/Ombi/ClientApp/app/login/loginoauth.component.ts +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.ts @@ -4,7 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router"; import { AuthService } from "../auth/auth.service"; @Component({ - templateUrl: "./loginoauth.component.html" + templateUrl: "./loginoauth.component.html", }) export class LoginOAuthComponent implements OnInit { public pin: number; @@ -18,7 +18,7 @@ export class LoginOAuthComponent implements OnInit { }); } - ngOnInit(): void { + public ngOnInit(): void { this.auth(); } diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts index 2f5b2dd79..bafe67756 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.ts @@ -1,7 +1,6 @@ import { Component } from "@angular/core"; import { Router } from "@angular/router"; - import { PlexService } from "../../services"; import { IdentityService, NotificationService, SettingsService } from "../../services"; import { AuthService } from "./../../auth/auth.service"; @@ -27,7 +26,7 @@ export class PlexComponent { return; } - this.identityService.createWizardUser({ + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, @@ -57,7 +56,7 @@ export class PlexComponent { return; } }); - } + }, ); } diff --git a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts index 7d0d0bf9d..25517541c 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts +++ b/src/Ombi/ClientApp/app/wizard/plex/plexoauth.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; -import { PlexOAuthService, IdentityService, SettingsService } from "../../services"; +import { IdentityService, PlexOAuthService, SettingsService } from "../../services"; import { AuthService } from "./../../auth/auth.service"; @Component({ @@ -23,15 +23,14 @@ export class PlexOAuthComponent implements OnInit { }); } - ngOnInit(): void { + public ngOnInit(): void { this.plexOauth.oAuth(this.pinId).subscribe(x => { if(!x.accessToken) { return; // RETURN } - - this.identityService.createWizardUser({ + this.identityService.createWizardUser({ username: "", password: "", usePlexAdminAccount: true, diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index cde78bc64..4819ed8a0 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -69,7 +69,6 @@ namespace Ombi.Controllers.External _log.LogDebug("Adding first server"); settings.Enable = true; - settings.UniqueInstallCode = Guid.NewGuid().ToString("N"); settings.Servers = new List { new PlexServers { diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 1f0d62147..0a515d9cb 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -143,10 +143,6 @@ namespace Ombi.Controllers public async Task PlexSettings() { var s = await Get(); - if (s.UniqueInstallCode.IsNullOrEmpty()) - { - s.UniqueInstallCode = Guid.NewGuid().ToString("N"); - } return s; } From e980ac6fc540595a5798c128ec2440a25bbe19e5 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Fri, 20 Apr 2018 10:48:25 +0100 Subject: [PATCH 040/495] Fixed styling !wip --- src/Ombi/ClientApp/app/wizard/plex/plex.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html index 5d69c435b..a48c0f73e 100644 --- a/src/Ombi/ClientApp/app/wizard/plex/plex.component.html +++ b/src/Ombi/ClientApp/app/wizard/plex/plex.component.html @@ -17,13 +17,13 @@ Please note we do not store this information, we only store your Plex Authorization Token that will allow Ombi to view your media and friends
- +
- +

OR

- +
From 738bdb46d72f3c1cd13c521548ec2fad89f03042 Mon Sep 17 00:00:00 2001 From: Anojh Date: Fri, 20 Apr 2018 14:02:19 -0700 Subject: [PATCH 041/495] Fix baseurl breaking themes --- src/Ombi/Controllers/SettingsController.cs | 6 ------ src/Ombi/Views/Shared/_Layout.cshtml | 10 ++++++++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 44702a073..9ffa9d81f 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -274,12 +274,6 @@ namespace Ombi.Controllers public async Task GetThemeContent([FromQuery]string url) { var css = await _githubApi.GetThemesRawContent(url); - var ombiSettings = await OmbiSettings(); - if (ombiSettings.BaseUrl != null) - { - int index = css.IndexOf("/api/"); - css = css.Insert(index, ombiSettings.BaseUrl); - } return Content(css, "text/css"); } diff --git a/src/Ombi/Views/Shared/_Layout.cshtml b/src/Ombi/Views/Shared/_Layout.cshtml index cafd75357..1f2610e1c 100644 --- a/src/Ombi/Views/Shared/_Layout.cshtml +++ b/src/Ombi/Views/Shared/_Layout.cshtml @@ -85,8 +85,14 @@ O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i @{ - if (customization.HasPresetTheme) - { + if (customization.HasPresetTheme) + { + if (!string.IsNullOrEmpty(baseUrl)) + { + var index = customization.PresetThemeContent.IndexOf("/api/"); + customization.PresetThemeContent = customization.PresetThemeContent.Insert(index, "/" + baseUrl); + } + From 2c08908c8b29bc9c9f26ef1a5f08a99321b09492 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 20 Apr 2018 23:29:00 +0100 Subject: [PATCH 042/495] Fixed the ombi login button !wip --- .../ClientApp/app/login/login.component.html | 65 +++++++++++-------- .../ClientApp/app/login/login.component.ts | 22 +------ .../app/login/loginoauth.component.html | 7 ++ .../app/login/loginoauth.component.ts | 7 ++ src/Ombi/Controllers/TokenController.cs | 9 +++ 5 files changed, 63 insertions(+), 47 deletions(-) diff --git a/src/Ombi/ClientApp/app/login/login.component.html b/src/Ombi/ClientApp/app/login/login.component.html index 528fe9917..50d887feb 100644 --- a/src/Ombi/ClientApp/app/login/login.component.html +++ b/src/Ombi/ClientApp/app/login/login.component.html @@ -9,39 +9,50 @@ include the remember me checkbox
-
-
+
+ +
+
+ +

-
- -
+ +
+ +
+ + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 3c2636b2f..5bc5f022a 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -29,26 +29,8 @@ export class LoginComponent implements OnDestroy, OnInit { public background: any; public landingFlag: boolean; public baseUrl: string; - - public get showLoginForm(): boolean { - if(this.customizationSettings.applicationUrl && this.plexEnabled) { - this.loginWithOmbi = false; - return false; - } - if(!this.customizationSettings.applicationUrl || !this.plexEnabled) { - - this.loginWithOmbi = true; - return true; - } - if(this.loginWithOmbi) { - return true; - } - - this.loginWithOmbi = true; - return true; - } - public loginWithOmbi: boolean = false; - + public loginWithOmbi: boolean; + public get appName(): string { if(this.customizationSettings.applicationName) { return this.customizationSettings.applicationName; diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.html b/src/Ombi/ClientApp/app/login/loginoauth.component.html index e80f1239f..e2118c7aa 100644 --- a/src/Ombi/ClientApp/app/login/loginoauth.component.html +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.html @@ -4,6 +4,13 @@
+ +
+ + + + Back +
diff --git a/src/Ombi/ClientApp/app/login/loginoauth.component.ts b/src/Ombi/ClientApp/app/login/loginoauth.component.ts index 03922d8b3..1adcff19c 100644 --- a/src/Ombi/ClientApp/app/login/loginoauth.component.ts +++ b/src/Ombi/ClientApp/app/login/loginoauth.component.ts @@ -8,6 +8,7 @@ import { AuthService } from "../auth/auth.service"; }) export class LoginOAuthComponent implements OnInit { public pin: number; + public error: string; constructor(private authService: AuthService, private router: Router, private route: ActivatedRoute) { @@ -24,11 +25,17 @@ export class LoginOAuthComponent implements OnInit { public auth() { this.authService.oAuth(this.pin).subscribe(x => { + if(x.access_token) { localStorage.setItem("id_token", x.access_token); if (this.authService.loggedIn()) { this.router.navigate(["search"]); + return; } + } + if(x.errorMessage) { + this.error = x.errorMessage; + } }); } diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index 624267989..b337b3f51 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -148,6 +148,15 @@ namespace Ombi.Controllers { var accessToken = await _plexOAuthManager.GetAccessTokenFromPin(pinId); + if (accessToken.IsNullOrEmpty()) + { + // Looks like we are not authenticated. + return new JsonResult(new + { + errorMessage = "Could not authenticate with Plex" + }); + } + // Let's look for the users account var account = await _plexOAuthManager.GetAccount(accessToken); From 77280cf4aea29b4cf2642b7269e29cd00bb14203 Mon Sep 17 00:00:00 2001 From: Avi Date: Fri, 20 Apr 2018 18:25:58 -0500 Subject: [PATCH 043/495] Sign In rather than Login/Continue --- src/Ombi/ClientApp/app/login/login.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/login/login.component.html b/src/Ombi/ClientApp/app/login/login.component.html index 50d887feb..a50040fab 100644 --- a/src/Ombi/ClientApp/app/login/login.component.html +++ b/src/Ombi/ClientApp/app/login/login.component.html @@ -43,11 +43,11 @@ include the remember me checkbox @@ -55,4 +55,4 @@ include the remember me checkbox - \ No newline at end of file + From 04af799efb02671d6b9d9214d4a2f2a527457654 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Sat, 21 Apr 2018 21:22:25 +0100 Subject: [PATCH 044/495] Fixed the newsletter not sending #2134 --- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 248 ++++++++++-------- .../Notifications/NewsletterSettings.cs | 2 +- 2 files changed, 133 insertions(+), 117 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 1a8b21fbd..6cfe67bb6 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using MailKit; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TvMaze; @@ -26,7 +27,7 @@ namespace Ombi.Schedule.Jobs.Ombi public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository addedLog, IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService custom, ISettingsService emailSettings, INotificationTemplatesRepository templateRepo, - UserManager um, ISettingsService newsletter) + UserManager um, ISettingsService newsletter, ILogger log) { _plex = plex; _emby = emby; @@ -42,6 +43,7 @@ namespace Ombi.Schedule.Jobs.Ombi _emailSettings.ClearCache(); _customizationSettings.ClearCache(); _newsletterSettings.ClearCache(); + _log = log; } private readonly IPlexContentRepository _plex; @@ -55,6 +57,7 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly ISettingsService _emailSettings; private readonly ISettingsService _newsletterSettings; private readonly UserManager _userManager; + private readonly ILogger _log; public async Task Start(NewsletterSettings settings, bool test) { @@ -74,152 +77,167 @@ namespace Ombi.Schedule.Jobs.Ombi return; } - var customization = await _customizationSettings.GetSettingsAsync(); + try + { - // Get the Content - var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); - var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); - var addedLog = _recentlyAddedLog.GetAll(); - var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); - var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var customization = await _customizationSettings.GetSettingsAsync(); - var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode).Select(x => x.ContentId); - var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).Select(x => x.ContentId); + // Get the Content + var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); + var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); - // Filter out the ones that we haven't sent yet - var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id)); - var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id)); + var addedLog = _recentlyAddedLog.GetAll(); + var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); - var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking(); - var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking(); + var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode).Select(x => x.ContentId); + var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).Select(x => x.ContentId); - var body = string.Empty; - if (test) - { - var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); - var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); - var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10); - var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10); - body = await BuildHtml(plexm, embym, plext, embyt, settings); - } - else - { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); - if (body.IsNullOrEmpty()) - { - return; - } + // Filter out the ones that we haven't sent yet + var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id)); + var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id)); + _log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count()); + _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count()); - } - - if (!test) - { - // Get the users to send it to - var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter); - if (!users.Any()) + var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking(); + var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking(); + + _log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count()); + _log.LogInformation("Emby Episodes to send: {0}", embyEpisodesToSend.Count()); + + var body = string.Empty; + if (test) { - return; + var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); + var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); + var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10); + var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10); + body = await BuildHtml(plexm, embym, plext, embyt, settings); } - - foreach (var emails in settings.ExternalEmails) + else { - users.Add(new OmbiUser + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); + if (body.IsNullOrEmpty()) { - UserName = emails, - Email = emails - }); + return; + } } - var emailTasks = new List(); - foreach (var user in users) + + if (!test) { - if (user.Email.IsNullOrEmpty()) + // Get the users to send it to + var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter); + if (!users.Any()) { - continue; + return; } - var messageContent = ParseTemplate(template, customization, user); - var email = new NewsletterTemplate(); + foreach (var emails in settings.ExternalEmails) + { + users.Add(new OmbiUser + { + UserName = emails, + Email = emails + }); + } + var emailTasks = new List(); + foreach (var user in users) + { + if (user.Email.IsNullOrEmpty()) + { + continue; + } - var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); + var messageContent = ParseTemplate(template, customization, user); + var email = new NewsletterTemplate(); - emailTasks.Add(_email.Send( - new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email }, - emailSettings)); - } + var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); - // Now add all of this to the Recently Added log - var recentlyAddedLog = new HashSet(); - foreach (var p in plexContentMoviesToSend) - { - recentlyAddedLog.Add(new RecentlyAddedLog + emailTasks.Add(_email.Send( + new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email }, + emailSettings)); + } + + // Now add all of this to the Recently Added log + var recentlyAddedLog = new HashSet(); + foreach (var p in plexContentMoviesToSend) { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Plex, - ContentType = ContentType.Parent, - ContentId = p.Id - }); + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentType = ContentType.Parent, + ContentId = p.Id + }); - } + } - foreach (var p in plexEpisodesToSend) - { - recentlyAddedLog.Add(new RecentlyAddedLog + foreach (var p in plexEpisodesToSend) { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Plex, - ContentType = ContentType.Episode, - ContentId = p.Id - }); - } + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentType = ContentType.Episode, + ContentId = p.Id + }); + } - foreach (var e in embyContentMoviesToSend) - { - if (e.Type == EmbyMediaType.Movie) + foreach (var e in embyContentMoviesToSend) + { + if (e.Type == EmbyMediaType.Movie) + { + recentlyAddedLog.Add(new RecentlyAddedLog + { + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Emby, + ContentType = ContentType.Parent, + ContentId = e.Id + }); + } + } + + foreach (var p in embyEpisodesToSend) { recentlyAddedLog.Add(new RecentlyAddedLog { AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, - ContentType = ContentType.Parent, - ContentId = e.Id + ContentType = ContentType.Episode, + ContentId = p.Id }); } - } + await _recentlyAddedLog.AddRange(recentlyAddedLog); - foreach (var p in embyEpisodesToSend) - { - recentlyAddedLog.Add(new RecentlyAddedLog - { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Emby, - ContentType = ContentType.Episode, - ContentId = p.Id - }); + await Task.WhenAll(emailTasks.ToArray()); } - await _recentlyAddedLog.AddRange(recentlyAddedLog); - - await Task.WhenAll(emailTasks.ToArray()); - } - else - { - var admins = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin); - foreach (var a in admins) + else { - if (a.Email.IsNullOrEmpty()) + var admins = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin); + foreach (var a in admins) { - continue; - } - var messageContent = ParseTemplate(template, customization, a); + if (a.Email.IsNullOrEmpty()) + { + continue; + } + var messageContent = ParseTemplate(template, customization, a); - var email = new NewsletterTemplate(); + var email = new NewsletterTemplate(); - var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); + var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo); - await _email.Send( - new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email }, - emailSettings); + await _email.Send( + new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email }, + emailSettings); + } } + + } + catch (Exception e) + { + _log.LogError(e, "Error when attempting to create newsletter"); + throw; } } @@ -285,8 +303,7 @@ namespace Ombi.Schedule.Jobs.Ombi } catch (Exception e) { - Console.WriteLine(e); - throw; + _log.LogError(e, "Error when Processing Plex Movies {0}", info.Title); } finally { @@ -315,7 +332,7 @@ namespace Ombi.Schedule.Jobs.Ombi theMovieDbId = result.id.ToString(); } - + var info = await _movieApi.GetMovieInformationWithExtraInfo(int.Parse(theMovieDbId)); if (info == null) { @@ -327,8 +344,7 @@ namespace Ombi.Schedule.Jobs.Ombi } catch (Exception e) { - Console.WriteLine(e); - throw; + _log.LogError(e, "Error when processing Emby Movies {0}", info.Title); } finally { @@ -437,7 +453,7 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append("
"); - + var title = $"{t.Title} ({t.ReleaseYear})"; Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/"); @@ -483,7 +499,7 @@ namespace Ombi.Schedule.Jobs.Ombi } catch (Exception e) { - //Log.Error(e); + _log.LogError(e, "Error when processing Plex TV {0}", t.Title); } finally { @@ -584,7 +600,7 @@ namespace Ombi.Schedule.Jobs.Ombi } catch (Exception e) { - //Log.Error(e); + _log.LogError(e, "Error when processing Emby TV {0}", t.Title); } finally { diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index f043cd74c..e79f3182c 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -7,6 +7,6 @@ namespace Ombi.Settings.Settings.Models.Notifications public bool DisableTv { get; set; } public bool DisableMovies { get; set; } public bool Enabled { get; set; } - public List ExternalEmails { get; set; } + public List ExternalEmails { get; set; } = new List(); } } \ No newline at end of file From 703203ed5c301de21d3e7b0fbe28886f8459aacd Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 21 Apr 2018 21:27:42 +0100 Subject: [PATCH 045/495] !wip --- src/Ombi.Core/Engine/RecentlyAddedEngine.cs | 32 +- src/Ombi.Helpers/LinqHelpers.cs | 7 + src/Ombi.Schedule/JobSetup.cs | 2 + src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 76 +- src/Ombi.Store/Entities/RecentlyAddedLog.cs | 4 +- ...180420225638_NewsletterChanges.Designer.cs | 956 ++++++++++++++++++ .../20180420225638_NewsletterChanges.cs | 33 + .../Migrations/OmbiContextModelSnapshot.cs | 4 + 8 files changed, 1091 insertions(+), 23 deletions(-) create mode 100644 src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.Designer.cs create mode 100644 src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs diff --git a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs index 8782ea028..114d32a24 100644 --- a/src/Ombi.Core/Engine/RecentlyAddedEngine.cs +++ b/src/Ombi.Core/Engine/RecentlyAddedEngine.cs @@ -63,13 +63,17 @@ namespace Ombi.Core.Engine var recentlyAddedLog = new HashSet(); foreach (var p in plexContent) { + if (!p.HasTheMovieDb) + { + continue; + } if (p.Type == PlexMediaTypeEntity.Movie) { recentlyAddedLog.Add(new RecentlyAddedLog { AddedAt = DateTime.Now, Type = RecentlyAddedType.Plex, - ContentId = p.Id, + ContentId = int.Parse(p.TheMovieDbId), ContentType = ContentType.Parent }); } @@ -78,12 +82,18 @@ namespace Ombi.Core.Engine // Add the episodes foreach (var ep in p.Episodes) { + if (!ep.Series.HasTvDb) + { + continue; + } recentlyAddedLog.Add(new RecentlyAddedLog { AddedAt = DateTime.Now, Type = RecentlyAddedType.Plex, - ContentId = ep.Id, - ContentType = ContentType.Episode + ContentId = int.Parse(ep.Series.TvDbId), + ContentType = ContentType.Episode, + EpisodeNumber = ep.EpisodeNumber, + SeasonNumber = ep.SeasonNumber }); } } @@ -91,13 +101,17 @@ namespace Ombi.Core.Engine foreach (var e in embyContent) { + if (e.TheMovieDbId.IsNullOrEmpty()) + { + continue; + } if (e.Type == EmbyMediaType.Movie) { recentlyAddedLog.Add(new RecentlyAddedLog { AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, - ContentId = e.Id, + ContentId = int.Parse(e.TheMovieDbId), ContentType = ContentType.Parent }); } @@ -106,12 +120,18 @@ namespace Ombi.Core.Engine // Add the episodes foreach (var ep in e.Episodes) { + if (ep.Series.TvDbId.IsNullOrEmpty()) + { + continue; + } recentlyAddedLog.Add(new RecentlyAddedLog { AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, - ContentId = ep.Id, - ContentType = ContentType.Episode + ContentId = int.Parse(ep.Series.TvDbId), + ContentType = ContentType.Episode, + EpisodeNumber = ep.EpisodeNumber, + SeasonNumber = ep.SeasonNumber }); } } diff --git a/src/Ombi.Helpers/LinqHelpers.cs b/src/Ombi.Helpers/LinqHelpers.cs index 279d161b7..67fdb5c53 100644 --- a/src/Ombi.Helpers/LinqHelpers.cs +++ b/src/Ombi.Helpers/LinqHelpers.cs @@ -14,5 +14,12 @@ namespace Ombi.Helpers yield return source1; } } + + public static HashSet ToHashSet( + this IEnumerable source, + IEqualityComparer comparer = null) + { + return new HashSet(source, comparer); + } } } \ No newline at end of file diff --git a/src/Ombi.Schedule/JobSetup.cs b/src/Ombi.Schedule/JobSetup.cs index 6626ef933..73d7c3536 100644 --- a/src/Ombi.Schedule/JobSetup.cs +++ b/src/Ombi.Schedule/JobSetup.cs @@ -66,6 +66,8 @@ namespace Ombi.Schedule RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s)); RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s)); + + BackgroundJob.Enqueue(() => _refreshMetadata.Start()); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 1a8b21fbd..75dc9723d 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using MailKit; @@ -84,23 +86,28 @@ namespace Ombi.Schedule.Jobs.Ombi var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); - var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode).Select(x => x.ContentId); - var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).Select(x => x.ContentId); + var addedPlexEpisodesLogIds = + addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode); + var addedEmbyEpisodesLogIds = + addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); // Filter out the ones that we haven't sent yet - var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id)); - var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id)); + var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(int.Parse(x.TheMovieDbId))); + var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(int.Parse(x.TheMovieDbId))); - var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking(); - var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking(); + + var plexEpisodesToSend = + FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), addedPlexEpisodesLogIds); + var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), + addedEmbyEpisodesLogIds); var body = string.Empty; if (test) { var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); - var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10); - var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10); + var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet(); + var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); body = await BuildHtml(plexm, embym, plext, embyt, settings); } else @@ -110,7 +117,6 @@ namespace Ombi.Schedule.Jobs.Ombi { return; } - } if (!test) @@ -157,7 +163,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Plex, ContentType = ContentType.Parent, - ContentId = p.Id + ContentId = int.Parse(p.TheMovieDbId), }); } @@ -169,7 +175,9 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Plex, ContentType = ContentType.Episode, - ContentId = p.Id + ContentId = int.Parse(p.Series.TvDbId), + EpisodeNumber = p.EpisodeNumber, + SeasonNumber = p.SeasonNumber }); } @@ -182,7 +190,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, ContentType = ContentType.Parent, - ContentId = e.Id + ContentId = int.Parse(e.TheMovieDbId), }); } } @@ -194,7 +202,9 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, ContentType = ContentType.Episode, - ContentId = p.Id + ContentId = int.Parse(p.Series.TvDbId), + EpisodeNumber = p.EpisodeNumber, + SeasonNumber = p.SeasonNumber }); } await _recentlyAddedLog.AddRange(recentlyAddedLog); @@ -229,6 +239,40 @@ namespace Ombi.Schedule.Jobs.Ombi await Start(newsletterSettings, false); } + private HashSet FilterPlexEpisodes(IEnumerable source, IQueryable recentlyAdded) + { + var itemsToReturn = new HashSet(); + foreach (var ep in source) + { + var tvDbId = int.Parse(ep.Series.TvDbId); + if (recentlyAdded.Any(x => x.ContentId == tvDbId && x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == ep.SeasonNumber)) + { + continue; + } + + itemsToReturn.Add(ep); + } + + return itemsToReturn; + } + + private HashSet FilterEmbyEpisodes(IEnumerable source, IQueryable recentlyAdded) + { + var itemsToReturn = new HashSet(); + foreach (var ep in source) + { + var tvDbId = int.Parse(ep.Series.TvDbId); + if (recentlyAdded.Any(x => x.ContentId == tvDbId && x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == ep.SeasonNumber)) + { + continue; + } + + itemsToReturn.Add(ep); + } + + return itemsToReturn; + } + private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings, OmbiUser username) { var resolver = new NotificationMessageResolver(); @@ -239,7 +283,7 @@ namespace Ombi.Schedule.Jobs.Ombi return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, IQueryable plexEpisodes, IQueryable embyEp, NewsletterSettings settings) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, HashSet plexEpisodes, HashSet embyEp, NewsletterSettings settings) { var sb = new StringBuilder(); @@ -366,7 +410,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddParagraph(sb, info.Overview); } - private async Task ProcessPlexTv(IQueryable plexContent, StringBuilder sb) + private async Task ProcessPlexTv(HashSet plexContent, StringBuilder sb) { var series = new List(); foreach (var plexEpisode in plexContent) @@ -494,7 +538,7 @@ namespace Ombi.Schedule.Jobs.Ombi } - private async Task ProcessEmbyTv(IQueryable embyContent, StringBuilder sb) + private async Task ProcessEmbyTv(HashSet embyContent, StringBuilder sb) { var series = new List(); foreach (var episode in embyContent) diff --git a/src/Ombi.Store/Entities/RecentlyAddedLog.cs b/src/Ombi.Store/Entities/RecentlyAddedLog.cs index ba26eb566..1ef091149 100644 --- a/src/Ombi.Store/Entities/RecentlyAddedLog.cs +++ b/src/Ombi.Store/Entities/RecentlyAddedLog.cs @@ -8,7 +8,9 @@ namespace Ombi.Store.Entities { public RecentlyAddedType Type { get; set; } public ContentType ContentType { get; set; } - public int ContentId { get; set; } // This is dependant on the type + public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID + public int? EpisodeNumber { get; set; } + public int? SeasonNumber { get; set; } public DateTime AddedAt { get; set; } } diff --git a/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.Designer.cs b/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.Designer.cs new file mode 100644 index 000000000..64c581108 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.Designer.cs @@ -0,0 +1,956 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using Ombi.Helpers; +using Ombi.Store.Context; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using System; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180420225638_NewsletterChanges")] + partial class NewsletterChanges + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + 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.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + 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.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + 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.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + 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.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs b/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs new file mode 100644 index 000000000..bea4d7080 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace Ombi.Store.Migrations +{ + public partial class NewsletterChanges : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EpisodeNumber", + table: "RecentlyAddedLog", + nullable: true); + + migrationBuilder.AddColumn( + name: "SeasonNumber", + table: "RecentlyAddedLog", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EpisodeNumber", + table: "RecentlyAddedLog"); + + migrationBuilder.DropColumn( + name: "SeasonNumber", + table: "RecentlyAddedLog"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 248551d97..794bde8a6 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -455,6 +455,10 @@ namespace Ombi.Store.Migrations b.Property("ContentType"); + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + b.Property("Type"); b.HasKey("Id"); From 443c79b0cae49ec43d8d6c52e42f0c6f2e0c9d69 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Sat, 21 Apr 2018 21:43:33 +0100 Subject: [PATCH 046/495] Fixed #2179. Note: This requires you to press the update database again before using the newsletter! --- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 16 ++++++++-------- .../20180420225638_NewsletterChanges.cs | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 75dc9723d..ea3276982 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -128,14 +128,14 @@ namespace Ombi.Schedule.Jobs.Ombi return; } - foreach (var emails in settings.ExternalEmails) - { - users.Add(new OmbiUser - { - UserName = emails, - Email = emails - }); - } + //foreach (var emails in settings.ExternalEmails) + //{ + // users.Add(new OmbiUser + // { + // UserName = emails, + // Email = emails + // }); + //} var emailTasks = new List(); foreach (var user in users) { diff --git a/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs b/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs index bea4d7080..ad4786772 100644 --- a/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs +++ b/src/Ombi.Store/Migrations/20180420225638_NewsletterChanges.cs @@ -17,6 +17,8 @@ namespace Ombi.Store.Migrations name: "SeasonNumber", table: "RecentlyAddedLog", nullable: true); + + migrationBuilder.Sql("DELETE FROM RecentlyAddedLog"); } protected override void Down(MigrationBuilder migrationBuilder) From b80ef04ab7b5f2d55349eae0420e274cc5f07e88 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Sat, 21 Apr 2018 22:04:44 +0100 Subject: [PATCH 047/495] !wip fixed merge --- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 104 +++++++++---------- 1 file changed, 47 insertions(+), 57 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index ba8725b69..79731637b 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -84,62 +84,51 @@ namespace Ombi.Schedule.Jobs.Ombi var customization = await _customizationSettings.GetSettingsAsync(); + // Get the Content + var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); + var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); - var addedPlexEpisodesLogIds = + var addedLog = _recentlyAddedLog.GetAll(); + var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + + var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode); - var addedEmbyEpisodesLogIds = - addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); + var addedEmbyEpisodesLogIds = + addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); - // Filter out the ones that we haven't sent yet - var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(int.Parse(x.TheMovieDbId))); - var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(int.Parse(x.TheMovieDbId))); + // Filter out the ones that we haven't sent yet + var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(int.Parse(x.TheMovieDbId))); + var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(int.Parse(x.TheMovieDbId))); _log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count()); _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count()); - var plexEpisodesToSend = - FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), addedPlexEpisodesLogIds); - var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), - addedEmbyEpisodesLogIds); + var plexEpisodesToSend = + FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), addedPlexEpisodesLogIds); + var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), + addedEmbyEpisodesLogIds); _log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count()); _log.LogInformation("Emby Episodes to send: {0}", embyEpisodesToSend.Count()); - var body = string.Empty; - if (test) - { - var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); - var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); - var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet(); - var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); - body = await BuildHtml(plexm, embym, plext, embyt, settings); - } - else - { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); - if (body.IsNullOrEmpty()) + var body = string.Empty; + if (test) { - return; + var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); + var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10); + var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet(); + var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); + body = await BuildHtml(plexm, embym, plext, embyt, settings); } - } - - if (!test) - { - // Get the users to send it to - var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter); - if (!users.Any()) - { - return; - } - - foreach (var emails in settings.ExternalEmails) + else { - users.Add(new OmbiUser + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); + if (body.IsNullOrEmpty()) { - UserName = emails, - Email = emails - }); + return; + } } - var emailTasks = new List(); - foreach (var user in users) + + if (!test) { // Get the users to send it to var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter); @@ -159,6 +148,7 @@ namespace Ombi.Schedule.Jobs.Ombi var emailTasks = new List(); foreach (var user in users) { + // Get the users to send it to if (user.Email.IsNullOrEmpty()) { continue; @@ -180,26 +170,26 @@ namespace Ombi.Schedule.Jobs.Ombi { recentlyAddedLog.Add(new RecentlyAddedLog { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Plex, - ContentType = ContentType.Parent, - ContentId = int.Parse(p.TheMovieDbId), - }); + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentType = ContentType.Parent, + ContentId = int.Parse(p.TheMovieDbId), + }); } foreach (var p in plexEpisodesToSend) - { - recentlyAddedLog.Add(new RecentlyAddedLog + { + recentlyAddedLog.Add(new RecentlyAddedLog { - AddedAt = DateTime.Now, - Type = RecentlyAddedType.Plex, - ContentType = ContentType.Episode, - ContentId = int.Parse(p.Series.TvDbId), - EpisodeNumber = p.EpisodeNumber, - SeasonNumber = p.SeasonNumber - }); - } + AddedAt = DateTime.Now, + Type = RecentlyAddedType.Plex, + ContentType = ContentType.Episode, + ContentId = int.Parse(p.Series.TvDbId), + EpisodeNumber = p.EpisodeNumber, + SeasonNumber = p.SeasonNumber + }); + } foreach (var e in embyContentMoviesToSend) { if (e.Type == EmbyMediaType.Movie) From 0ce9fb2df94e292bfa061a8d8ca33dedd6722b2c Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Sat, 21 Apr 2018 22:25:53 +0100 Subject: [PATCH 048/495] Fixed #2170 --- .../Repository/SettingsJsonRepository.cs | 1 + src/Ombi/Program.cs | 2 +- src/Ombi/Startup.cs | 28 ++++++++----------- 3 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/Ombi.Store/Repository/SettingsJsonRepository.cs b/src/Ombi.Store/Repository/SettingsJsonRepository.cs index 09bf61695..248413ccc 100644 --- a/src/Ombi.Store/Repository/SettingsJsonRepository.cs +++ b/src/Ombi.Store/Repository/SettingsJsonRepository.cs @@ -81,6 +81,7 @@ namespace Ombi.Store.Repository public void Update(GlobalSettings entity) { + Db.Update(entity); //_cache.Remove(GetName(entity.SettingsName)); Db.SaveChanges(); } diff --git a/src/Ombi/Program.cs b/src/Ombi/Program.cs index 9294852f9..7e9fa6f78 100644 --- a/src/Ombi/Program.cs +++ b/src/Ombi/Program.cs @@ -82,7 +82,7 @@ namespace Ombi ctx.SaveChanges(); } } - else if(!baseUrl.Equals(dbBaseUrl.Value)) + else if(baseUrl.HasValue() && !baseUrl.Equals(dbBaseUrl.Value)) { dbBaseUrl.Value = baseUrl; ctx.SaveChanges(); diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 7fc0522ed..55a49fa92 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -173,26 +173,22 @@ namespace Ombi settings.ApiKey = Guid.NewGuid().ToString("N"); ombiService.SaveSettings(settings); } - if (settings.BaseUrl.HasValue()) - { - app.UsePathBase(settings.BaseUrl); - } - else + + // Check if it's in the startup args + var appConfig = serviceProvider.GetService(); + var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl).Result; + if (baseUrl != null) { - // Check if it's in the startup args - var appConfig = serviceProvider.GetService(); - var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl).Result; - if (baseUrl != null) + if (baseUrl.Value.HasValue()) { - if (baseUrl.Value.HasValue()) - { - settings.BaseUrl = baseUrl.Value; - ombiService.SaveSettings(settings); - - app.UsePathBase(settings.BaseUrl); - } + settings.BaseUrl = baseUrl.Value; + ombiService.SaveSettings(settings); } } + if (settings.BaseUrl.HasValue()) + { + app.UsePathBase(settings.BaseUrl); + } app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 1, ServerTimeout = TimeSpan.FromDays(1), ShutdownTimeout = TimeSpan.FromDays(1)}); app.UseHangfireDashboard(settings.BaseUrl.HasValue() ? $"{settings.BaseUrl}/hangfire" : "/hangfire", From a0dbd1c442b5e32ccab40335e378988bd57fce89 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Sat, 21 Apr 2018 22:47:01 +0100 Subject: [PATCH 049/495] Fixed #2151 --- src/Ombi.Core/Engine/Interfaces/BaseEngine.cs | 15 +-------------- src/Ombi.Store/Context/OmbiContext.cs | 16 ++++++++++++++++ src/Ombi.Store/Entities/User.cs | 1 + src/Ombi/Controllers/IdentityController.cs | 2 +- src/Ombi/Properties/launchSettings.json | 1 + 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index e608ffebb..26bc5969c 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -32,14 +32,7 @@ namespace Ombi.Core.Engine.Interfaces private OmbiUser _user; protected async Task GetUser() { - if (IsApiUser) - { - return new OmbiUser - { - UserName = Username, - }; - } - return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == Username)); + return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(Username, StringComparison.CurrentCultureIgnoreCase))); } protected async Task UserAlias() @@ -49,10 +42,6 @@ namespace Ombi.Core.Engine.Interfaces protected async Task IsInRole(string roleName) { - if (IsApiUser && roleName != OmbiRoles.Disabled) - { - return true; - } return await UserManager.IsInRoleAsync(await GetUser(), roleName); } @@ -72,7 +61,5 @@ namespace Ombi.Core.Engine.Interfaces var ruleResults = await Rules.StartSpecificRules(model, rule); return ruleResults; } - - private bool IsApiUser => Username.Equals("Api", StringComparison.CurrentCultureIgnoreCase); } } \ No newline at end of file diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index e4c9be516..d1963e765 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -123,7 +123,23 @@ namespace Ombi.Store.Context { NormalizedName = OmbiRoles.RecievesNewsletter.ToUpper() }); + SaveChanges(); } + + // Make sure we have the API User + var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase)); + if (!apiUserExists) + { + Users.Add(new OmbiUser + { + UserName = "Api", + UserType = UserType.SystemUser, + NormalizedUserName = "API", + + }); + SaveChanges(); + } + //Check if templates exist var templates = NotificationTemplates.ToList(); diff --git a/src/Ombi.Store/Entities/User.cs b/src/Ombi.Store/Entities/User.cs index d9a3207b2..68d1dbe00 100644 --- a/src/Ombi.Store/Entities/User.cs +++ b/src/Ombi.Store/Entities/User.cs @@ -29,6 +29,7 @@ namespace Ombi.Store.Entities { public enum UserType { + SystemUser = 0, LocalUser = 1, PlexUser = 2, EmbyUser = 3, diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index 5db5f2168..fe63226e6 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -213,7 +213,7 @@ namespace Ombi.Controllers [PowerUser] public async Task> GetAllUsers() { - var users = await UserManager.Users + var users = await UserManager.Users.Where(x => x.UserType != UserType.LocalUser) .ToListAsync(); var model = new List(); diff --git a/src/Ombi/Properties/launchSettings.json b/src/Ombi/Properties/launchSettings.json index ec5deb319..33794436c 100644 --- a/src/Ombi/Properties/launchSettings.json +++ b/src/Ombi/Properties/launchSettings.json @@ -10,6 +10,7 @@ "profiles": { "IIS Express": { "commandName": "IISExpress", + "commandLineArgs": "-baseurl /testing", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" From 8927c5fa9cc312bfbd84882925a117fd348deec2 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Sat, 21 Apr 2018 22:51:56 +0100 Subject: [PATCH 050/495] Fixed #2164 --- src/Ombi.Api.Pushover/PushoverApi.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ombi.Api.Pushover/PushoverApi.cs b/src/Ombi.Api.Pushover/PushoverApi.cs index 96f4d2e95..4ac08c0f5 100644 --- a/src/Ombi.Api.Pushover/PushoverApi.cs +++ b/src/Ombi.Api.Pushover/PushoverApi.cs @@ -1,4 +1,5 @@ using System; +using System.Net; using System.Net.Http; using System.Threading.Tasks; using Ombi.Api.Pushover.Models; @@ -17,7 +18,7 @@ namespace Ombi.Api.Pushover public async Task PushAsync(string accessToken, string message, string userToken) { - var request = new Request($"messages.json?token={accessToken}&user={userToken}&message={message}", PushoverEndpoint, HttpMethod.Post); + var request = new Request($"messages.json?token={accessToken}&user={userToken}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post); var result = await _api.Request(request); return result; From 0c6083dd9af91275b0ab88050cd5420b89dffc1c Mon Sep 17 00:00:00 2001 From: Anojh Date: Sat, 21 Apr 2018 22:26:25 -0700 Subject: [PATCH 051/495] detect if baseurl is already set, and reset the link --- src/Ombi/Views/Shared/_Layout.cshtml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Ombi/Views/Shared/_Layout.cshtml b/src/Ombi/Views/Shared/_Layout.cshtml index 1f2610e1c..8433f7b4e 100644 --- a/src/Ombi/Views/Shared/_Layout.cshtml +++ b/src/Ombi/Views/Shared/_Layout.cshtml @@ -88,10 +88,20 @@ O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i if (customization.HasPresetTheme) { if (!string.IsNullOrEmpty(baseUrl)) + { + if (!customization.PresetThemeContent.Contains("/" + baseUrl)) { var index = customization.PresetThemeContent.IndexOf("/api/"); customization.PresetThemeContent = customization.PresetThemeContent.Insert(index, "/" + baseUrl); + } else + { + var startIndex = customization.PresetThemeContent.IndexOf("href="); + var index = customization.PresetThemeContent.IndexOf("/api/"); + customization.PresetThemeContent = customization.PresetThemeContent.Remove(startIndex+6, (index-(startIndex+6))); + index = customization.PresetThemeContent.IndexOf("/api/"); + customization.PresetThemeContent = customization.PresetThemeContent.Insert(index, "/" + baseUrl); } + } + } if (!string.IsNullOrEmpty(customization.CustomCssLink)) { - + } } From b8c64dff2d1e014e6feb872fb9b4f4b7876f0f3b Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Mon, 23 Apr 2018 16:27:33 +0100 Subject: [PATCH 054/495] Update Hangfire, Newtonsoft and Swagger --- src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj | 2 +- src/Ombi.Api/Ombi.Api.csproj | 2 +- src/Ombi.Core/Ombi.Core.csproj | 4 ++-- src/Ombi.Helpers/Ombi.Helpers.csproj | 2 +- src/Ombi.Schedule/Ombi.Schedule.csproj | 6 +++--- src/Ombi.Settings/Ombi.Settings.csproj | 2 +- src/Ombi.Store/Ombi.Store.csproj | 2 +- src/Ombi/Ombi.csproj | 6 +++--- src/Ombi/Startup.cs | 1 - 9 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj b/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj index ca15bae05..83318be7b 100644 --- a/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj +++ b/src/Ombi.Api.Mattermost/Ombi.Api.Mattermost.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Ombi.Api/Ombi.Api.csproj b/src/Ombi.Api/Ombi.Api.csproj index 325f316b8..8379691c5 100644 --- a/src/Ombi.Api/Ombi.Api.csproj +++ b/src/Ombi.Api/Ombi.Api.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index c2af094bb..de6f5c8fa 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -11,12 +11,12 @@ - + - + diff --git a/src/Ombi.Helpers/Ombi.Helpers.csproj b/src/Ombi.Helpers/Ombi.Helpers.csproj index 00cd8d5e9..9bb599c2a 100644 --- a/src/Ombi.Helpers/Ombi.Helpers.csproj +++ b/src/Ombi.Helpers/Ombi.Helpers.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Ombi.Schedule/Ombi.Schedule.csproj b/src/Ombi.Schedule/Ombi.Schedule.csproj index 5088bc9f8..47e599e80 100644 --- a/src/Ombi.Schedule/Ombi.Schedule.csproj +++ b/src/Ombi.Schedule/Ombi.Schedule.csproj @@ -10,9 +10,9 @@ - - - + + + diff --git a/src/Ombi.Settings/Ombi.Settings.csproj b/src/Ombi.Settings/Ombi.Settings.csproj index 5a99cc830..3cb56cb07 100644 --- a/src/Ombi.Settings/Ombi.Settings.csproj +++ b/src/Ombi.Settings/Ombi.Settings.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Ombi.Store/Ombi.Store.csproj b/src/Ombi.Store/Ombi.Store.csproj index 901882669..522a96957 100644 --- a/src/Ombi.Store/Ombi.Store.csproj +++ b/src/Ombi.Store/Ombi.Store.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index f5e523db7..7f3cd8055 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -61,8 +61,8 @@ - - + + @@ -78,7 +78,7 @@ - + diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index 55a49fa92..be78c5b11 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -219,7 +219,6 @@ namespace Ombi app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); - c.ShowJsonEditor(); }); app.UseMvc(routes => From f24b0f20eb35ff6a1899dad09b7c564bb58c03ec Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Mon, 23 Apr 2018 16:30:02 +0100 Subject: [PATCH 055/495] Updated Mailkit dependancy --- src/Ombi.Notifications/Ombi.Notifications.csproj | 2 +- src/Ombi/Ombi.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Notifications/Ombi.Notifications.csproj b/src/Ombi.Notifications/Ombi.Notifications.csproj index ccdb29e65..46a64072e 100644 --- a/src/Ombi.Notifications/Ombi.Notifications.csproj +++ b/src/Ombi.Notifications/Ombi.Notifications.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 7f3cd8055..89e432580 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -67,7 +67,7 @@ - + From 6806b97c1b19158d697196248a1701cf80dd384c Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 23 Apr 2018 21:15:03 +0100 Subject: [PATCH 056/495] Fixed the bug where only showing API User #2187 --- src/Ombi/Controllers/IdentityController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index fe63226e6..8a9c76a34 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -213,7 +213,7 @@ namespace Ombi.Controllers [PowerUser] public async Task> GetAllUsers() { - var users = await UserManager.Users.Where(x => x.UserType != UserType.LocalUser) + var users = await UserManager.Users.Where(x => x.UserType != UserType.SystemUser) .ToListAsync(); var model = new List(); From f62e97bb32818c3b454ca6f347053e82e1029866 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 24 Apr 2018 08:09:11 +0100 Subject: [PATCH 057/495] Fixed bug #2188 #2134 --- src/Ombi.Helpers/StringHelper.cs | 10 ++++++++++ src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 18 +++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Ombi.Helpers/StringHelper.cs b/src/Ombi.Helpers/StringHelper.cs index daa8cabe7..aba120c65 100644 --- a/src/Ombi.Helpers/StringHelper.cs +++ b/src/Ombi.Helpers/StringHelper.cs @@ -65,5 +65,15 @@ namespace Ombi.Helpers securePassword.MakeReadOnly(); return securePassword; } + + public static int IntParseLinq(string stringIn) + { + if (int.TryParse(stringIn, out var result)) + { + return result; + } + + return -1; + } } } \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 79731637b..8b246e97e 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -98,8 +98,8 @@ namespace Ombi.Schedule.Jobs.Ombi addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); // Filter out the ones that we haven't sent yet - var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(int.Parse(x.TheMovieDbId))); - var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(int.Parse(x.TheMovieDbId))); + var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); + var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); _log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count()); _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count()); @@ -173,7 +173,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Plex, ContentType = ContentType.Parent, - ContentId = int.Parse(p.TheMovieDbId), + ContentId = StringHelper.IntParseLinq(p.TheMovieDbId), }); } @@ -185,7 +185,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Plex, ContentType = ContentType.Episode, - ContentId = int.Parse(p.Series.TvDbId), + ContentId = StringHelper.IntParseLinq(p.Series.TvDbId), EpisodeNumber = p.EpisodeNumber, SeasonNumber = p.SeasonNumber }); @@ -199,7 +199,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, ContentType = ContentType.Parent, - ContentId = int.Parse(e.TheMovieDbId), + ContentId = StringHelper.IntParseLinq(e.TheMovieDbId), }); } } @@ -211,7 +211,7 @@ namespace Ombi.Schedule.Jobs.Ombi AddedAt = DateTime.Now, Type = RecentlyAddedType.Emby, ContentType = ContentType.Episode, - ContentId = int.Parse(p.Series.TvDbId), + ContentId = StringHelper.IntParseLinq(p.Series.TvDbId), EpisodeNumber = p.EpisodeNumber, SeasonNumber = p.SeasonNumber }); @@ -259,7 +259,7 @@ namespace Ombi.Schedule.Jobs.Ombi var itemsToReturn = new HashSet(); foreach (var ep in source) { - var tvDbId = int.Parse(ep.Series.TvDbId); + var tvDbId = StringHelper.IntParseLinq(ep.Series.TvDbId); if (recentlyAdded.Any(x => x.ContentId == tvDbId && x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == ep.SeasonNumber)) { continue; @@ -276,7 +276,7 @@ namespace Ombi.Schedule.Jobs.Ombi var itemsToReturn = new HashSet(); foreach (var ep in source) { - var tvDbId = int.Parse(ep.Series.TvDbId); + var tvDbId = StringHelper.IntParseLinq(ep.Series.TvDbId); if (recentlyAdded.Any(x => x.ContentId == tvDbId && x.EpisodeNumber == ep.EpisodeNumber && x.SeasonNumber == ep.SeasonNumber)) { continue; @@ -374,7 +374,7 @@ namespace Ombi.Schedule.Jobs.Ombi theMovieDbId = result.id.ToString(); } - var info = await _movieApi.GetMovieInformationWithExtraInfo(int.Parse(theMovieDbId)); + var info = await _movieApi.GetMovieInformationWithExtraInfo(StringHelper.IntParseLinq(theMovieDbId)); if (info == null) { continue; From e12146c98666d5de0471b106e918ff8ea7568468 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 24 Apr 2018 08:32:31 +0100 Subject: [PATCH 058/495] More improvements to the Plex OAuth, Added the ability to turn it off if needed --- .../Authentication/PlexOAuthManager.cs | 19 ++++++++++++++----- src/Ombi.Core/IPlexOAuthManager.cs | 2 +- src/Ombi.Helpers/UriHelper.cs | 1 + .../Settings/Models/External/PlexSettings.cs | 1 + .../ClientApp/app/interfaces/ISettings.ts | 1 + .../ClientApp/app/login/login.component.html | 9 +++++---- .../ClientApp/app/login/login.component.ts | 8 +++++++- .../app/settings/plex/plex.component.html | 6 ++++++ src/Ombi/Controllers/SettingsController.cs | 2 +- src/Ombi/Controllers/TokenController.cs | 5 +++-- 10 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index d3bab0a05..f3a3e4d01 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -55,15 +55,24 @@ namespace Ombi.Core.Authentication return await _api.GetAccount(accessToken); } - public async Task GetOAuthUrl(int pinId, string code) + public async Task GetOAuthUrl(int pinId, string code, string websiteAddress = null) { - var settings = await _customizationSettingsService.GetSettingsAsync(); - if (settings.ApplicationUrl.IsNullOrEmpty()) + Uri url; + if (websiteAddress.IsNullOrEmpty()) { - return null; + var settings = await _customizationSettingsService.GetSettingsAsync(); + if (settings.ApplicationUrl.IsNullOrEmpty()) + { + return null; + } + + url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl, false); + } + else + { + url = _api.GetOAuthUrl(pinId, code, websiteAddress, false); } - var url = _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl, false); return url; } diff --git a/src/Ombi.Core/IPlexOAuthManager.cs b/src/Ombi.Core/IPlexOAuthManager.cs index 142d4162a..9c4f0582e 100644 --- a/src/Ombi.Core/IPlexOAuthManager.cs +++ b/src/Ombi.Core/IPlexOAuthManager.cs @@ -9,7 +9,7 @@ namespace Ombi.Core.Authentication { Task GetAccessTokenFromPin(int pinId); Task RequestPin(); - Task GetOAuthUrl(int pinId, string code); + Task GetOAuthUrl(int pinId, string code, string websiteAddress = null); Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress); Task GetAccount(string accessToken); } diff --git a/src/Ombi.Helpers/UriHelper.cs b/src/Ombi.Helpers/UriHelper.cs index 83cd27e9d..6ec8047ae 100644 --- a/src/Ombi.Helpers/UriHelper.cs +++ b/src/Ombi.Helpers/UriHelper.cs @@ -112,6 +112,7 @@ namespace Ombi.Helpers return uriBuilder.Uri; } + } public class ApplicationSettingsException : Exception diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index 3faba3e42..a77b54a87 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -6,6 +6,7 @@ namespace Ombi.Core.Settings.Models.External public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } + public bool EnableOAuth { get; set; } public List Servers { get; set; } } diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index d67ebc698..234e0aa5b 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -43,6 +43,7 @@ export interface IEmbyServer extends IExternalSettings { export interface IPlexSettings extends ISettings { enable: boolean; + enableOAuth: boolean; servers: IPlexServer[]; } diff --git a/src/Ombi/ClientApp/app/login/login.component.html b/src/Ombi/ClientApp/app/login/login.component.html index a50040fab..40a5ef5a0 100644 --- a/src/Ombi/ClientApp/app/login/login.component.html +++ b/src/Ombi/ClientApp/app/login/login.component.html @@ -17,7 +17,7 @@ include the remember me checkbox

-
+
-
+
+
+ + +
+
diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 0a515d9cb..1bbb5af49 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -154,7 +154,7 @@ namespace Ombi.Controllers var s = await Get(); - return s.Enable; + return s.Enable && s.EnableOAuth; } /// diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index b337b3f51..a488ba172 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -23,7 +23,7 @@ namespace Ombi.Controllers { [ApiV1] [Produces("application/json")] - public class TokenController + public class TokenController : Controller { public TokenController(OmbiUserManager um, IOptions ta, IAuditRepository audit, ITokenRepository token, IPlexOAuthManager oAuthManager) @@ -83,8 +83,9 @@ namespace Ombi.Controllers // We need a PIN first var pin = await _plexOAuthManager.RequestPin(); + var websiteAddress = $"{this.Request.Scheme}://{this.Request.Host}{this.Request.PathBase}"; //https://app.plex.tv/auth#?forwardUrl=http://google.com/&clientID=Ombi-Test&context%5Bdevice%5D%5Bproduct%5D=Ombi%20SSO&pinID=798798&code=4lgfd - var url = await _plexOAuthManager.GetOAuthUrl(pin.id, pin.code); + var url = await _plexOAuthManager.GetOAuthUrl(pin.id, pin.code, websiteAddress); if (url == null) { return new JsonResult(new From c244faca7d28a138ce2287e27dd9ddfc33efd22f Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 24 Apr 2018 08:34:48 +0100 Subject: [PATCH 059/495] !wip changelog --- CHANGELOG.md | 3655 +------------------------------------------------- 1 file changed, 45 insertions(+), 3610 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecb1a790d..49d85aca7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,51 @@ ### **New Features** +- Updated Mailkit dependancy. [Jamie Rees] + +- Update Hangfire, Newtonsoft and Swagger. [Jamie Rees] + +- Added View on Emby Button (#2173) [Anojh Thayaparan] + +- Added background property to tvrequests API (#2172) [Anojh Thayaparan] + +### **Fixes** + +- More improvements to the Plex OAuth, Added the ability to turn it off if needed. [Jamie] + +- Fixed bug #2188 #2134. [Jamie] + +- Fixed the bug where only showing API User #2187. [Jamie] + +- Detect if baseurl is already set, and reset the link. [Anojh] + +- Fixed #2164. [Jamie Rees] + +- Fixed #2151. [Jamie Rees] + +- Fixed #2170. [Jamie Rees] + +- Fixed the newsletter not sending #2134. [Jamie Rees] + +- Fix baseurl breaking themes. [Anojh] + +- Inject base url if set before theme file url, see issue #1795. [Anojh] + +- Sign In rather than Login/Continue. [Avi] + +- Fixed #2179. [Jamie Rees] + +- Fixed #2169. [Jamie Rees] + +- Knocking out LC requirements in issue #2124 (#2125) [Anojh Thayaparan] + +- Inject base url if set before theme file url, see issue #1795 (#2148) [Anojh Thayaparan] + + +## v3.0.3185 (2018-04-16) + +### **New Features** + - Added a new Job. Plex Recently Added, this is a slimmed down version of the Plex Sync job, this will just scan the recently added list and not the whole library. I'd reccomend running this very regulary and the full scan not as regular. [Jamie] ### **Fixes** @@ -819,3613 +864,3 @@ - Switch to use a single HTTPClient rather than a new one every request !dev. [tidusjar] -- Fix non-admin rights (#1820) [Rob Gökemeijer] - -- Fix duplicated "Requests" element ID on new Issues link (#1817) [Shoghi Cervantes] - -- Add the Issue Reporting functionality (#1811) [Jamie] - -- Removed the forum. [tidusjar] - -- #1659 Made the option to ignore notifcations for auto approve. [Jamie] - -- New Crowdin translations (#1806) [Jamie] - -- Fixed a launch issue. [Jamie] - -- Allow users to login without a password. [Jamie] - -- Fixed the emby notifications not being sent. [Jamie] - -- #1802 and other small fixes. [tidusjar] - -- So... This sickrage thing should work now. [tidusjar] - -- Fixed emby connect login issue. [tidusjar] - -- Stop making unnecessary calls to the update service. [Jamie] - -- Fixed a bug where it blocked users with 0 limits. [Jamie] - -- Done #1788. [tidusjar] - -- More logging. [Jamie] - -- Fixed #1738. [Jamie] - -- Fixed build. [Jamie] - -- Fixed the issue where notifications were not sendind unless we restarted #1732. [tidusjar] - -- Fixed an issue with a trailing space in the subdir. [tidusjar] - -- Fixed #1774. [Jamie] - -- #1773. [Jamie] - -- Roll back rxjs (#1778) [bazhip] - -- Fixed build. [Jamie] - -- Fixed #1763. [Jamie] - -- Fix "content length error" on preview gif (#1768) [OoGuru] - -- New preview gif for Ombi V3 README (#1767) [OoGuru] - -- Remove debug code. [tidusjar] - -- Fix #1762. [tidusjar] - -- Fixed the preset themes not loading. [tidusjar] - -- Fixed #1760 and improvements on the auto updater. We may now support windows services... #1460. [Jamie] - -- Fixed #1754. [Jamie] - -- Hide the subject when it's not being used. [Jamie] - -- Error handling #1749. [Jamie] - -- New Crowdin translations (#1741) [Jamie] - -- #1732 #1722 #1711. [Jamie] - -- Fixed an issue with switching the preset themes. [Jamie] - -- Fixed #1743. [Jamie] - -- Fixed #1742. [tidusjar] - -- Fix #1742. [tidusjar] - -- Fixed landing page. [Jamie] - -- Fixed. [Jamie] - -- Translated the Requests page and fixed #1740. [Jamie] - -- Fix crash. [Jamie] - -- Sickrage done. Ish... So i've written all the code by looking at the API. the key there is i've looked at the api. I have not tested anything so expect this to fail. [Jamie] - -- SickRage settings UI. [Jamie] - -- Fixed #1721. [tidusjar] - -- Fixed the preset themes issue. [tidusjar] - -- New Crowdin translations (#1654) [Jamie] - -- Fix build. [Jamie] - -- #1460. [Jamie] - -- Fixed tests. [Jamie] - -- Return css as MIME text/css. [Jamie] - -- More added for the preset themes. [Jamie] - -- Moved around the custom styles. [Jamie] - -- More renames. [Jamie] - -- Renames. [Jamie] - -- Load the first 100 requests. [Jamie] - -- Reduce the memory consumption #1720. [Jamie] - -- Moved the schedules jobs into it's own database, see if it helps with the db locking #1720. [Jamie] - -- Fixed #1712. [tidusjar] - -- Potential fix for #1702. [tidusjar] - -- Fixed #1708. [tidusjar] - -- Fixed #1677. [tidusjar] - -- Fixed build. [tidusjar] - -- Potential fix for the DB locking issue #1720. [tidusjar] - -- #1698. [Jamie] - -- Fixed #1705. [tidusjar] - -- Fixed #1703. [tidusjar] - -- Finished adding preset themes. [Jamie] - -- Fixed #17000. [Jamie] - -- Remove the themes because waiting for a merge from lerams project. [Jamie] - -- Finsihed adding preset themes. [Jamie] - -- Fixed #1677. [Jamie] - -- Temp fix for #1683. [Jamie] - -- Fixed #1685. [Jamie] - -- Lossless Compression of images saves 83 KB (#1676) [Fish2] - -- Fixed the availability checker. [tidusjar] - -- Fixed build. [tidusjar] - -- Push out missing migration. [tidusjar] - -- Potential fix for #1674. [tidusjar] - -- Fixed an issue with the caching. [tidusjar] - -- Fixed telegram #1667. [tidusjar] - -- Fixed #1663. [tidusjar] - -- Should fix #1663. [tidusjar] - -- Stop logged in users going to the login page. [Jamie] - -- Fixed it not updating. Styles should be good now. [Jamie] - -- Re did some of the styling on the movie search page, let me know your thoughts. [Jamie] - -- Fixed #1657. [Jamie] - -- Fixed #1655. [Jamie] - -- Removed authentication resul. [Jamie] - -- New Crowdin translations (#1651) [Jamie] - -- New Crowdin translations (#1648) [Jamie] - -- New Crowdin translations (#1638) [Jamie] - -- Fixed #1644. [Jamie] - -- Moar logs #1643. [tidusjar] - -- Fixed #1640. [tidusjar] - -- Fixed the null ref exception #1460. [tidusjar] - -- Fixed landing page. [TidusJar] - -- Fixed #1641. [TidusJar] - -- Fixed #1641. [TidusJar] - -- New Crowdin translations (#1635) [Jamie] - -- Fixed #1631 and improved translation support Included startup args for the auto updater #1460 Mark TV requests as available #1632. [tidusjar] - -- Remove 32bit. [Jamie] - -- More 32bit support. [Jamie] - -- We now show "Available" for tv shows that is fully available #1602. [tidusjar] - -- Fixed the issue where we have got an episode but not the related series. #1620. [tidusjar] - -- Fixed the dropdown not working on iOS in the settings #1615. [tidusjar] - -- Fixed sonarr not monitoring the latest season #1534. [tidusjar] - -- Fixed the issue with firefox #1544. [tidusjar] - -- Fixed discord #1623. [tidusjar] - -- Add browserstack thanks (#1627) [Matt Jeanes] - -- Fix the exception #1613. [Jamie] - -- Found where we potentially are setting a new poster path, looks like the entity was being modified and being set as Tracked by entity framework, so the next time we called SaveChangesAsync() it would save the new posterpath on the entity. [Jamie] - -- Small modifications. [Jamie] - -- Fixed #1622. [Jamie] - -- Various improvements to webpack/gulp/vscode support (#1617) [Matt Jeanes] - -- Episodes in requests are now in order #1597 (#1614) [masterhuck] - -- Fixed a null reference issue in the Plex Content Cacher. [Jamie.Rees] - -- Fixed #1610. [tidusjar] - -- Really fixed the build this time. [tidusjar] - -- Fixed build. [tidusjar] - -- Made the updater work again #1460. [tidusjar] - -- Adding logging into the auto updater and also added more logging around the create inital user for #1604. [tidusjar] - -- Fixed the issue where we did not check if they are already in sonarr when choosing certain options #1540. [tidusjar] - -- We can now delete tv child requests and the parent will get remove #1603. [tidusjar] - -- Finished the api changes requested #1601. [tidusjar] - -- Fixed the Hangfire server timeout issue #1605. [tidusjar] - -- Fixed notifications not sending #1594. [tidusjar] - -- Fixed #1583 you can now delete users. Fixed the issue where the requested by was not showing. Finally fixed the broken poster paths. [tidusjar] - -- Fixed the issue where movie requests were no longer being requested. [tidusjar] - -- Started adding some more unit tests #1596. [Jamie.Rees] - -- #1588 When we make changes to any requests that we can trigger a notification, always send it to all notification agents, even if the user wont recieve it. [Jamie.Rees] - -- Add a message when email notifications are not setup when requesting a password reset. #1590. [Jamie.Rees] - -- Removed text that we no longer need. [Jamie.Rees] - -- Fixed #1574. [Jamie.Rees] - -- #1460 looks like the permissions issue has been resolved. Just need to make sure the Ombi process is terminated. [Jamie.Rees] - -- Put back the old download code. [Jamie.Rees] - -- Test. [Jamie] - -- Build sln. [Jamie.Rees] - -- Order by the username #1581. [Jamie.Rees] - -- Remove sonarr episodes from the cache table. [Jamie.Rees] - -- Couchpotato finished. [tidusjar] - -- Disable run import button if no import options are selected. [tidusjar] - -- Fixed #1574. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixes the issue with non windows systems unable to unzip the tarball #1460. [tidusjar] - -- Finished the couchpotato settings. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed #1570 #1571. [tidusjar] - -- Fixed #1547. [tidusjar] - -- Should fix #1538. [tidusjar] - -- Fixed #1553. [tidusjar] - -- Fixed #1546. [tidusjar] - -- Fixed #1543. [tidusjar] - -- Fixes an issue with Movie caching not working on develop branch of Radarr (#1567) [Jeffrey Peters] - -- This adds two fields to the Email Notifications settings page. It allows for the disabling of TLS/SSL as well as the ability to disable certificate validation when sending notification emails. (#1552) [Jeffrey Peters] - -- Fixed typo (#1551) [Codehhh] - -- Use Sqlite storage for Hangfire. [tidusjar] - -- Fixed the overrides #1539 also display it on screen now too. [tidusjar] - -- Fixed #1542 also added VSCode support. [tidusjar] - -- Fixed some cosmetic issues #865. [Jamie.Rees] - -- Fixed #1531. [Jamie.Rees] - -- Small fixes #865. [Jamie.Rees] - -- Some errors fixed and some ui improvements #865. [tidusjar] - -- Auto-scale large images down to container size (#1529) [Avi] - -- Fix logo on login page. (#1528) [Avi] - -- Another potential issue? :/ [tidusjar] - -- Real fix. [tidusjar] - -- #1513 Added storage path. [Jamie.Rees] - -- Fixed the discord issue relating to images #1513. [Jamie.Rees] - -- Fixed the issue sending movies to Radarr #1513 Fixed typo #1524. [Jamie.Rees] - -- Fixed logo on reset password pages fixed the run importer button on the user management settings. [Jamie.Rees] - -- Fixed crash/error #865. [tidusjar] - -- #1513 fixed the landing page and also the reverse proxy images. [tidusjar] - -- #1513 correctly set the child requests as approved. [tidusjar] - -- Fixed an issue that potentially causes as issue when siging into plex #865. [tidusjar] - -- Remove dev branch. [PotatoQuality] - -- Prepare readme for upcoming beta. [PotatoQuality] - -- #1513 partially fixed a bug. [tidusjar] - -- Fixed the exception. [tidusjar] - -- Fixed the application url not saving #1513. [tidusjar] - -- Fixed liniting. [tidusjar] - -- REVERSE PROXY BITCH! #1513. [tidusjar] - -- Fixed a bug where we were marking the wrong episodes as available #1513 #865. [Jamie.Rees] - -- Fixed an issue where we messed up the pages and routing. [Jamie.Rees] - -- Emby user importer is now therer! #1456. [tidusjar] - -- #1513 Added the update available icon. [tidusjar] - -- Fixed the issue of it showing as not requested when we find it in Radarr. Made the tv shows match a bit more to the movie requests Added the ability for plex and emby users to login Improved the welcome email, will only show for users that have not logged in Fixed discord notifications the about screen now checks if there is an update ready #1513. [tidusjar] - -- Support email addresses as usernames #1513. [Jamie.Rees] - -- Link to issue treath. [PotatoQuality] - -- Give correct feedback when testing email notifications #1513. [Jamie.Rees] - -- Report issue removed and the deny dropdown removed #1513. [Jamie.Rees] - -- #1513 removed the discord text when testing pushbullet. [Jamie.Rees] - -- Made a lot of changes around the notifcations to support the custom app name also started on the welcome email ##1456. [Jamie.Rees] - -- Fixed the bug where we were displaying shows where we do not have enough information to request #1513. [Jamie.Rees] - -- #1513 added the network to tv shows. [Jamie.Rees] - -- Fixed the whitespace issue #1513. [Jamie.Rees] - -- Fixed the swagger endpoint #865 #1513 Fixed the custom image issue on the login page Fixed the bug when clicking on the tab on the requests page it would switch to the wrong one Swagger is now back @ /swagger. [tidusjar] - -- Optimized images, Update old compressed image with a new lossless one. (#1514) [camjac251] - -- #1513 #865 Fixed the issue where we do not send the requests to Radarr/Sonarr when approving. [tidusjar] - -- #1506 #865 Fixed an issue with the test buttons not working correctly. [tidusjar] - -- #865 Added donation link. [tidusjar] - -- Fixed a bunch of issues on #1513. [tidusjar] - -- #1460 Added the Updater, it all seems to be working correctly. #865. [Jamie.Rees] - -- Removed percentage. [Jamie.Rees] - -- Fixed linter. [Jamie.Rees] - -- Fixed some bugs in the UI #865. [Jamie.Rees] - -- Improved the search buttons #865. [Jamie.Rees] - -- More logging #865. [Jamie.Rees] - -- Made build faster. [Jamie.Rees] - -- More logging. [Jamie.Rees] - -- Set debug level to Debug for now. [Jamie.Rees] - -- Add linting and indexes for interfaces/services (#1510) [Matt Jeanes] - -- Fixed the issue with the tv search not working #1463. [Jamie.Rees] - -- Latest practices... also probably broke some styles - sorry (#1508) [Matt Jeanes] - -- Build with the branch version. [tidusjar] - -- Build fix. [tidusjar] - -- Fixed build. [tidusjar] - -- Omgwtf so many changes. #865. [tidusjar] - -- Tests. [Jamie.Rees] - -- #1456 Started on the User Importer Also added the remember me button. [Jamie.Rees] - -- Made some UI changes, reworked the Emby and Plex screens to make them more user friendly and no so fugly. #865 Also made the login page placeholder text slightly lighter. [Jamie.Rees] - -- Cake skip verification build stuff #865. [Jamie.Rees] - -- Some fixes around the UI and managing requests #865. [tidusjar] - -- #1486. [Jamie.Rees] - -- #1486. [Jamie.Rees] - -- Upgraded to .net core 2.0 #1486. [Jamie.Rees] - -- #865 Finished the landing page, we now check the server's status. [Jamie.Rees] - -- Fixed build. [TidusJar] - -- Removed the telegram api. [Jamie.Rees] - -- Small changes on the updater #1460 #865. [Jamie.Rees] - -- Remove unused functions. [Dhruv Bhavsar] - -- Make Episode picker similar to Requests Child view. #1457 #1463. [Dhruv Bhavsar] - -- Fix merge conflict for TvRequests component. [Dhruv Bhavsar] - -- Upstream Changes... [Dhruv Bhavsar] - -- Clean up Requests page code by moving children request to old component, remove additional REST calls when merging and update component names to make more sense. [Dhruv Bhavsar] - -- Lots of different UI enhancements and fixes #865. [tidusjar] - -- Gitchangelog. [tidusjar] - -- Fixed the issue where we were using the wrong availability options. [tidusjar] - -- Fixed a bunch of bugs in Ombi #865. [tidusjar] - -- Build versioning. [Jamie.Rees] - -- #1460 The assembly versioning seems to work correctly now. [Jamie.Rees] - -- More build versioning changes #865. [tidusjar] - -- Fixed cake script. [Jamie.Rees] - -- WIP on the build versioning for the Updater #1460 #865. [Jamie.Rees] - -- Versioning. [Jamie.Rees] - -- Package versions. [Jamie.Rees] - -- #1460 #865 working on the auto updater. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Small changes around the roles #865. [tidusjar] - -- Improvements to the UI and also finished the availability checker #865 #1464. [Jamie.Rees] - -- Availability Checker #1464 #865. [Jamie.Rees] - -- Fixed ##1492 and finished the episode searcher for #1464. [Jamie.Rees] - -- #1464. [tidusjar] - -- Reload the settings #1464 #865. [Jamie.Rees] - -- #1464 added the Plex episode cacher #865. [Jamie.Rees] - -- Fixed some issues around the tv requests area Added mattermost and telegram notifications #1459 #865 #1457. [tidusjar] - -- Fix global.json. [Dhruv Bhavsar] - -- Working UI for Requests. Approval/Deny does not work as it doesn't in your code either. [Dhruv Bhavsar] - -- Enable diagnostic on build #865. [Jamie.Rees] - -- Fixed the user token issue #865. [Jamie.Rees] - -- Some small refresh token work #865. [Jamie.Rees] - -- Initial TV Requests UI rebuild. [Dhruv Bhavsar] - -- Made a start on supporting multiple emby servers, the UI needs rework #865. [Jamie.Rees] - -- #865 #1459 Added the Sender From field for email notifcations. We can now have "Friendly Names" for email notifications. [Jamie.Rees] - -- Redirect to the landing page when enabled #1458 #865. [Jamie.Rees] - -- Removed IdentityServer, it was overkill #865. [Jamie.Rees] - -- Fixed another bug with identity. #865 I'm thinking about removing it. Causing more hassle than it's worth. [tidusjar] - -- #1460 #865. [tidusjar] - -- Delete appveyor_old.yml. [Jamie] - -- Fixed path. [Jamie.Rees] - -- Silent build level. [Jamie.Rees] - -- #1459 Forgot to get the Pushbullet agent to look up the pusbullet templates rather than the Discord ones. Updated the Gitchange log. [Jamie.Rees] - -- Made the placeholder color on the login page a bit lighter #865. [Jamie.Rees] - -- Landing and login page changes #865 #1485. [tidusjar] - -- #1458 #865 More work on landing. [Jamie.Rees] - -- Working on the landing page #1458 #865. [tidusjar] - -- A lot of clean up and added a new Image api #865. [Jamie.Rees] - -- Cleaned up the Logging API slightly #1465 #865. [Jamie.Rees] - -- Fixed the Identity Server discovery bug #1456 #865. [tidusjar] - -- Fixed the issue with the Identity Server running on a different port, we can now use -url #865. [Jamie.Rees] - -- Try again. [TidusJar] - -- Publish ubuntu 16.04. [Jamie.Rees] - -- Chnaged the updater job from Minutely to Hourly. [Jamie.Rees] - -- Some work around the Auto Updater and other small changes #1460 #865. [Jamie.Rees] - -- Missed a file. [tidusjar] - -- Fixed the swagger issue. [tidusjar] - -- RDP issues. [tidusjar] - -- Appveyor build rdp investigation. [tidusjar] - -- Working on the requests page #1457 #865. [tidusjar] - -- Made the password reset email style the same as other email notifications #1456 #865. [Jamie.Rees] - -- Fixed some bugs around the authentication #1456 #865. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Fixed build #1456. [Jamie.Rees] - -- #1456 #865 Started on allowing Plex Users to sign in through the new authentication server. [Jamie.Rees] - -- Removed covalent. [Jamie.Rees] - -- #1456 Reset Password stuff #865. [Jamie.Rees] - -- Finished implimenting Identity with IdentityServer4. #865 #1456. [Jamie.Rees] - -- Moved over to using Identity Server with Asp.Net Core Identity #1456 #865. [Jamie.Rees] - -- Started on the requests rework #865. [Jamie.Rees] - -- Extended the Emby API. [Jamie.Rees] - -- Started reworking the usermanagement page #1456 #865. [tidusjar] - -- Lots of refactoring #865. [Jamie.Rees] - -- Created an individual user api endpoint so we can make the user management pages better #865. [TidusJar] - -- Lot's of refactoring. [Jamie.Rees] - -- #1462 #865 Had to refactor how we use notificaitons. So we now have more notification fields about the request. [Jamie.Rees] - -- Looks like Sonarr is finished and works. A lot simplier this time around. #865. [tidusjar] - -- More work on the Sonarr Api Integration #865. [tidusjar] - -- Started on sonarr #865. [tidusjar] - -- Small changes #865. [tidusjar] - -- Damn son. So many changes... Fixed alot of stuff around tv episodes with the new DB model #865. [tidusjar] - -- Fixed the TV Requests issue #865. [Jamie.Rees] - -- Fixed a load of bugs need to figure out what is wrong with tv requests #865. [tidusjar] - -- #865 rework the backend data. Actually use real models rather than a JSON store. [Jamie.Rees] - -- Fixed the build issue #865. [tidusjar] - -- Allow us to use Emby as a media server. [tidusjar] - -- More Update #865. [Jamie.Rees] - -- Deployment changes. [Jamie.Rees] - -- More work on the Updater. [Jamie.Rees] - -- Lots of fixes. Becoming more stable now. #865. [tidusjar] - -- Small fixes around the searching. [Jamie.Rees] - -- Some rules #865. [Jamie.Rees] - -- Oops. [TidusJar] - -- Started on the Discord API settings page. [TidusJar] - -- Email Notifications are now fully customizable and work! #865. [Jamie.Rees] - -- Small changes and fixed some stylingon the plex page #865. [Jamie.Rees] - -- More on #865 TODO, Find out whats going on with the notifications and why exceptions are being thrown. [Jamie.Rees] - -- Oops. [Jamie.Rees] - -- Ok #865 fixed the published exe. [Jamie.Rees] - -- Fixed errors. [Jamie.Rees] - -- Fixed build script. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Some more #865. [Jamie.Rees] - -- Create appveyor.yml. [Jamie] - -- The Approving child requests now work! [Jamie.Rees] - -- Fixed many bugs #865. [Jamie.Rees] - -- Loads of changes, improved the movie search stylings is back. [Jamie.Rees] - -- Moved to webpack and started on new style. [Jamie.Rees] - -- Fixed the TV search via Trakt not returning Images anymore. #865. [Jamie.Rees] - -- Rules changes and rework. [Jamie.Rees] - -- Request Grid test. [Jamie.Rees] - -- Small cleanup #865. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Started the Radarr Settings #865. [Jamie.Rees] - -- Massive amount of rework on the plex settings page. It's pretty decent now! #865. [tidusjar] - -- Fixed build. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Rules #865. [tidusjar] - -- Stuff. [Jamie.Rees] - -- Forgot to uncomment. [Jamie.Rees] - -- Tetsd. [Jamie.Rees] - -- Build task changes. [Jamie.Rees] - -- Adsa. [Jamie.Rees] - -- Appveyor. [Jamie.Rees] - -- Stuff around tokens and also builds. [Jamie.Rees] - -- Finished the Plex Content Cacher. Need to do the episodes part but things are now showing as available! #865. [tidusjar] - -- Small user changes #865. [Jamie.Rees] - -- Stuff #865 need to work on the claims correctly. [Jamie.Rees] - -- Reworked the TV model AGAIN #865. [Jamie.Rees] - -- The move! [Jamie.Rees] - -- Fixed build #865. [Jamie.Rees] - -- Fixed the user management #865. [Jamie.Rees] - -- #865 Added support for multiple plex servers. [Jamie.Rees] - -- Bleh. [tidusjar] - -- Small changes. [Jamie.Rees] - -- Fixed the build. [Jamie.Rees] - -- Fixes. [Jamie.Rees] - -- Bundling changes. [Jamie.Rees] - -- Some series information stuff, changes the pace theme too. [Jamie.Rees] - -- Docker support and more, redesign the episodes. [tidusjar] - -- Stuff around episode/season searching/requesting. [Jamie.Rees] - -- Removed redundant folders. [tidusjar] - -- Lots of backend work. [tidusjar] - -- Fixed build. [Jamie.Rees] - -- TV Request stuff. [Jamie.Rees] - -- Work around the user management. [tidusjar] - -- More. [Jamie.Rees] - -- Lots and Lots of work. [Jamie.Rees] - -- Diagnostic changes. [tidusjar] - -- Fixed hangfire exception. [tidusjar] - -- Remove xunit. [tidusjar] - -- Lots more work :( [Jamie.Rees] - -- More changes. [tidusjar] - -- #865. [Jamie.Rees] - -- Small changes. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- More mapping. [Jamie.Rees] - -- Mapping mainly. [Jamie.Rees] - -- Fix systemjs config not being included. [Matt Jeanes] - -- Fixed bundling and various improvements. [Matt Jeanes] - -- Finished the emby wizard #865. [tidusjar] - -- Finished the wizard #865 (For Plex Anyway) [tidusjar] - -- Small changes. [tidusjar] - -- More work on Wizard and Plex API #865. [tidusjar] - -- Settings. [Jamie.Rees] - -- Settings for Ombi. [Jamie.Rees] - -- Fixed some issues around the identity. [Jamie.Rees] - -- #865 more for the authentication. [tidusjar] - -- Auth. [Jamie.Rees] - -- More on the search and requests page. It's almost there for movies. Need to add some filtering logic #865. [tidusjar] - -- #865. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Messing around with the settings. [tidusjar] - -- Fixed the yml. [Jamie.Rees] - -- Remove unneeded bundle config. [Matt Jeanes] - -- Redo dotnet publish targets. [Jamie.Rees] - -- Bundling changes. [Jamie.Rees] - -- Stuff. [Jamie.Rees] - -- Move app into wwwroot. [Jamie.Rees] - -- Put uglify back in! [Jamie.Rees] - -- Wrong line. [Jamie.Rees] - -- Matt is helping. [Jamie.Rees] - -- Revert. [tidusjar] - -- Small tweaks. [tidusjar] - -- Upgrade to .Net Standard 1.6. [tidusjar] - - -## v2.2.1 (2017-04-09) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the forums. [tidusjar] - -- Updates. [tidusjar] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update gulpfile.js. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added a retry policy around the emby newsletter. [Jamie.Rees] - -### **Fixes** - -- Revert "Merge branch 'DotNetCore' into dev" [tidusjar] - -- More borken build. [Jamie.Rees] - -- Started adding requesting. [Jamie.Rees] - -- Done the movie searching. [tidusjar] - -- #865. [tidusjar] - -- More. [tidusjar] - -- Moar. [tidusjar] - -- Small changes. [tidusjar] - -- Styling. [Jamie.Rees] - -- MOre changes. [Jamie.Rees] - -- Spacing. [Jamie.Rees] - -- Try again. [Jamie.Rees] - -- More. [Jamie.Rees] - -- Again. [Jamie.Rees] - -- Anbother. [Jamie.Rees] - -- Another. [Jamie.Rees] - -- Another. [Jamie.Rees] - -- Retry. [Jamie.Rees] - -- A. [Jamie.Rees] - -- Fixed. [Jamie.Rees] - -- Cahnge 2. [Jamie.Rees] - -- Appveyor change. [Jamie.Rees] - -- The start of a new world. [Jamie.Rees] - -- Fixed the migration number and order by the added date for the newsletter #1264. [tidusjar] - -- Forgot this change. [tidusjar] - -- Also fixed the issue for the Emby Newsletter where episodes were not getting added :( [tidusjar] - -- #1264 "They may take our lives, but they'll never take our freedom!" [tidusjar] - -- Finished reworking the Sonarr Integration. Seems to be working as expected, faster and most stable. It's Not A Toomah! [tidusjar] - -- Small bit of work. [Jamie.Rees] - -- Made a start on the new Sonarr integration. [tidusjar] - -- For test emails, if there is no new content then just grab some old data. [tidusjar] - -- Fixed an issue where the emby newsletter was always showing series. [tidusjar] - - -## v2.2.0 (2017-03-30) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added a new setting for the Netflix option, we can now disable it appearing in the search. [tidusjar] - -- Update German Translation. [Marius Schiffer] - -- Added a release notes page, you can access via Admin>Updates>Recent Changes tab. Note to self, need to put better comments in for users to understand! [Jamie.Rees] - -- Added gravitar image. [Jamie.Rees] - -- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] - -- Added a missing `await` for an HP AddArtist call. Added some more Trace logging. [smcpeck] - -- Added some logging around API calls. [smcpeck] - -- Changed IEmbyAvailabilityChecker to use IEnumberables + checking actor search against Emby content + PR feedback. [smcpeck] - -- Changed actor searching to support non-actors too. [smcpeck] - -- Added a 10 second timer to refresh some new caching I put in. [smcpeck] - -- Added root folder and approving quality profiles in radarr #1065. [tidusjar] - -- Added some debugging code around the newsletter for Emby #1116. [tidusjar] - -- Added a TMDB Rate limiter for the newsletter. [tidusjar] - -- Added port check in wizard. also fixed favicon. [tidusjar] - -- Update Radarr placeholder. [d2dyno] - -- Added the user login for emby users #435. [tidusjar] - -- Added User Management support for Emby #435. [tidusjar] - -- Added emby to the sidebar #435. [tidusjar] - -- Added API endpoint for /actor/new/ to support searching for movies not already available/requested. [smcpeck] - -- Update ISSUE_TEMPLATE.md. [Jamie] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -### **Fixes** - -- Translation changes. [Jamie.Rees] - -- Syntax error. [tidusjar] - -- Fixed an issue where we were retrying the API call when the Plex users login creds were invalid. #1217. [tidusjar] - -- Slightly increased the wait time for the emby newsletter also fixed a potential error in the plex user checker. [Jamie.Rees] - -- Fixed an issue where we were not notifiying emby users. [Jamie.Rees] - -- Fixed the issue where the recent changes page was not showing the correct date. #1296. [Jamie.Rees] - -- Fixed #1252 (Show the correct user type on the management page for Plex Users) [Jamie.Rees] - -- Fixed the casting error #1292. [Jamie.Rees] - -- Fix test newletter not sending when empty. [Dhruv Bhavsar] - -- Quick fix for email false positive message. ISSUE: #1286. [Dhruv Bhavsar] - -- Fixes around the newsletter. We will now correctly show newly added shows and also newly added episodes. #1163. [tidusjar] - -- Fixed a sonarr deseralization error. [tidusjar] - -- Increased the delay for the Episode information api calls. #1163. [tidusjar] - -- Looks like we were overloading emby with out api calls. [tidusjar] - -- Fixed the root path escaping issue for Radarr too! [tidusjar] - -- Some small backend newsletter changes, we can now detect if there are any movies and/or tv shows, if there are none then we will no longer send out an empty newsletter. [Jamie.Rees] - -- Remoddeled the notificaiton settings to make it easier to add more. This is some techinical changes that no one except me will ever notice :( [Jamie.Rees] - -- Fixed #1234. [Jamie.Rees] - -- A fix to the about page and also started to rework the notification backend slightly to easily add more notifications. [Jamie.Rees] - -- Adding more logging into the Plex Cacher. [Jamie.Rees] - -- #1218 changed the text when we cannot display release notes for dev and EAP branches. [Jamie.Rees] - -- Fix for #1236. [SuperPotatoMen] - -- Tooltips. [Jamie.Rees] - -- #236. [Jamie.Rees] - -- #1102. [Jamie.Rees] - -- Done #1012. [Jamie.Rees] - -- Oops #1134. [Jamie.Rees] - -- Fixed #1121. [Jamie.Rees] - -- Fixed #1210. [Jamie.Rees] - -- Fixed typo #1134. [Jamie.Rees] - -- Fixed #1223. [Jamie.Rees] - -- Another newsletter fix attempt #1163 #1116. [tidusjar] - -- Fixup! Reset the branch on v2.1.0 tag to get to a shared state between dev and Master. [distaula] - -- Fixed a bug in the Plex Newsletter. [tidusjar] - -- Typo. [tidusjar] - -- Fixed around the newsletter and a small feature around the permissions/features (#1215) [Jamie] - -- Fixed #1189. [tidusjar] - -- Fixed #1195. [Jamie.Rees] - -- Fixed #1195. [Jamie.Rees] - -- Fixed #1192. [Jamie.Rees] - -- Fixed issue where we could get null rating keys on Plex. [tidusjar] - -- Needed to treat a 201 as success, too. + removed some commented out code. [Shaun McPeck] - -- Normalized spacing/tabs. [smcpeck] - -- Move local user login to be the first thing checked; renamed old Api variable to PlexApi now that Emby is in play. [smcpeck] - -- Remove all the polling/retry logic around HP requests. This was a problem do to not properly awaiting the initial AddArtist API call being sent to HP. Also fix SetAlbumStatus to use ReleaseId instead of MusicBrainsId (same fix previously applied to AddArtist). [smcpeck] - -- Restore checking of HTTP StatusCode on ApiRequests; remove checking of response.ErrorException. [smcpeck] - -- Reverted (for now) non-200 response handling; added some extra logging. [smcpeck] - -- Tweaked ApiRequest behavior on non-200 responses; think it was breaking login. :-" [smcpeck] - -- Only deserialize response payload in ApiRequest when StatusCode == 200. Will a default return value in other cases cause other issues? [smcpeck] - -- Headphones - added releaseID to generic RequestedModel and passing that through to HP request. Their API doesn't request via the MusicBrainzId. [smcpeck] - -- Fixed #1038. [tidusjar] - -- Fixed a slight issue where we could click the change folders button rather than the dropdown arrow #1189. [tidusjar] - -- Bunch of updater files. [tidusjar] - -- #1163 #117. [tidusjar] - -- Removed some unnecessary 'ConfigureAwait` uses. [smcpeck] - -- Remove meaningless html class from actor searching checkbox. [smcpeck] - -- Fixed an issue where we were not always showing movies from external programs. [tidusjar] - -- Remove extra delay when filtering out existing movies. [smcpeck] - -- Post merge build fixes. [smcpeck] - -- Fix. [tidusjar] - -- Fixed #1177. [tidusjar] - -- Fixed #1152. [tidusjar] - -- Fixed #1123. [tidusjar] - -- Fixed a bug when sending to radarr. [tidusjar] - -- Fixed #1133. [tidusjar] - -- Fixed issues img. [Jamie.Rees] - -- Stop Plex being enabled on the first time installing #1048. [Jamie.Rees] - -- The landing page now works for emby #435. [tidusjar] - -- Fixed #1104. [tidusjar] - -- Fixed #1090. [tidusjar] - -- Fixed #1103. [tidusjar] - -- Small changes. [tidusjar] - -- Break out Mass Email feature into its own tab, upgrade Font Awesome and clean up some comments. [dhruvb14] - -- Fix typo. [Travis Bybee] - -- Fixed #1066. [Jamie.Rees] - -- Fixed broken builds. [Jamie.Rees] - -- Fixed #1083. [Jamie.Rees] - -- #1049. [tidusjar] - -- Fixed #1071. [tidusjar] - -- Fixed #1048 #1081. [tidusjar] - -- #1074. [Jamie.Rees] - -- #1069. [Jamie.Rees] - -- Fix for #1068. [tidusjar] - -- Remove duplciate tv show status. [tidusjar] - -- Some request ui changes. [tidusjar] - -- Removed references to Plex. [tidusjar] - -- Removed plex from the scheduled jobs ui. [tidusjar] - -- First run of the newsletter set it to a test. [tidusjar] - -- Reworked the newsletter for Emby! Need to rework it for Plex and use the new way to do it. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed the mass email, it was only being set to users with the newsletter feature #358. [tidusjar] - -- Removed Plex Request from the notifications. [tidusjar] - -- Finish implementing mass email feature. [dhruvb14] - -- @tidusjar pointed out runtime error!! [dhruvb14] - -- Does not compile, need to get data from UI into nancy somehow and figure out why IMassEmail is not initializing. [dhruvb14] - -- Begin Implementing Mass Email Section. [dhruvb14] - -- Hide the auto update btn #236 Fixed where we were not populating the emby episodes #435. [tidusjar] - -- Fix Radarr labels. [d2dyno] - -- Fixed pace loader. [Jamie.Rees] - -- Check if Emby/Plex is enabled before starting the job. [Jamie.Rees] - -- Fixed #1036. [Jamie.Rees] - -- Fixed a typo and changed wording. [Torkil Liseth] - -- Fixed #1035. [Jamie.Rees] - -- Fix for #1026. [Jamie.Rees] - -- Fixed #1042. [tidusjar] - -- #435. [Jamie.Rees] - -- #435 Started the wizard. [Jamie.Rees] - -- Removed. [tidusjar] - -- Final Fixes. [dhruvb14] - -- Partial fix for broken HR tag's in Email... [dhruvb14] - -- DAMN! #435 that's a lot of code! [tidusjar] - -- Started adding Emby, Lots of backend work done. Need a few more services done and login and user management. #435. [tidusjar] - -- UI changes to add checkbox and support searching for only new matches via new API. [smcpeck] - -- REFACTOR: IAvailabilityChecker - changed arrays to IEnumerables. [smcpeck] - -- UI changes to consume actor searching API. [smcpeck] - -- API changes to allow for searching movies by actor. [smcpeck] - -- Enforcing async/await in synchronous methods that were marked async. [smcpeck] - - -## v2.1.0 (2017-01-31) - -### **New Features** - -- Update README.md. [Jamie] - -- Update .gitattributes. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added the new labels to the search. [tidusjar] - -- Added a switch to use the new search or not, just in case people do not like it. added a migration to turn on the new search. [Jamie.Rees] - -- Added a bunch of categories for tv search similar to what we have for movies. [Jamie.Rees] - -### **Fixes** - -- Fixed typos. [Haries Ramdhani] - -- Fix typo in readme. [tdorsey] - -- Fixed #985. [Jamie.Rees] - -- FIxed #978. [tidusjar] - -- Fixed the approval issue for #939. [tidusjar] - -- Some general improvements. [tidusjar] - -- Turned off migration for now. [tidusjar] - -- Fixed #998. [tidusjar] - -- Additional movie information. [Jamie.Rees] - -- Debug info around the notifications. [Jamie.Rees] - -- Small changes. [tidusjar] - -- Fixed #995. [tidusjar] - -- Fix for #978. [tidusjar] - -- Fixed #991. [tidusjar] - -- Fixed the login issue and pass Radarr the year #990. [Jamie.Rees] - -- More small tweaks around the username/alias. [Jamie.Rees] - -- Possible issue with the empty username. [Jamie.Rees] - -- Potential Fix for #985. [Jamie.Rees] - -- Small changed to the sidebar. [Jamie.Rees] - -- Small changes. [Jamie.Rees] - -- Done #627. [Jamie.Rees] - -- Finished #535 #445 #170. [tidusjar] - -- Fixed tests. [Jamie.Rees] - -- Started to add the specify Sonarr root folders. [Jamie.Rees] - -- Fixed #968. [Jamie.Rees] - -- Fixed #970. [Jamie.Rees] - -- Done #924. [Jamie.Rees] - -- Fixed. [Jamie.Rees] - -- Fixed #955. [Jamie.Rees] - -- #956. [Jamie.Rees] - -- Fixed #947. [Jamie.Rees] - -- #951. [tidusjar] - -- Finished #923 !!! [tidusjar] - -- More for #923. [Jamie.Rees] - -- Radarr integartion in progress #923. [tidusjar] - -- Fixed #940 don't show any shows without a tvdb id. [tidusjar] - -- Finished #739. [Jamie.Rees] - -- Initial impliementation of #739. [Jamie.Rees] - -- Improved the search UI and made it more consistant. Finished the Netflix API Part #884. [Jamie.Rees] - - -## v2.0.1 (2017-01-16) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added a netflix api. [Jamie.Rees] - -### **Fixes** - -- Fixed #934. [Jamie.Rees] - - -## v2.0 (2017-01-14) - -### **New Features** - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Update README.md. [SuperPotatoMen] - -- Added the settings for #925 but need to apply the settings to the UI. [Jamie.Rees] - -- Changed the settings name from Plex Requests to Ombi. [Jamie.Rees] - -- Added support for Managed Users #811. [Jamie.Rees] - -- Change solution name in travis. [mhann] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -### **Fixes** - -- Finished #925. [Jamie.Rees] - -- Some TODO's. [Jamie.Rees] - -- Fixed #915. [Jamie.Rees] - -- Fixed the issue where notifications are not being sent to users with Aliases #912. [Jamie.Rees] - -- Fixed #891. [Jamie.Rees] - -- Fix indentation issue. [Marcus Hann] - -- Implement simple button. [Marcus Hann] - -- Plex Username Case Sensitivity Fix. [thegame3202] - -- Fixed #882. [Jamie.Rees] - -- Api changed again, so more fixes for #878. [Jamie.Rees] - -- Possible fix for #893. [Jamie.Rees] - -- Fixed #898. [Jamie.Rees] - -- Fixed #878. [TidusJar] - -- * userManagementController.js: fixed #881. [TidusJar] - -- More work on watcher, should all be good now. #878. [Jamie.Rees] - -- Delete PlexRequests.sln.DotSettings. [Jamie] - -- Fixed #862. [Jamie.Rees] - -- #399 and #398 finished. [Jamie.Rees] - -- More work on #399. [Jamie.Rees] - -- Finished #884. [Jamie.Rees] - -- More for #844. [Jamie.Rees] - -- Another #844. [Jamie.Rees] - -- Fixed a dependancy issue with #844. [Jamie.Rees] - -- Finished the main part of #844 just need testing. [Jamie.Rees] - -- Fixed #832. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Fix tiny readme typo. [mhann] - -- Fixed #850 also started #844 (Wrote the API interaction) [Jamie.Rees] - -- #801 #292 done. [Jamie.Rees] - -- Should fix #841 #835 #810. [Jamie.Rees] - -- Fix small typo in ticket overview page. [mhann] - -- More work on the combined login. [Jamie.Rees] - -- Fixed db issue. [Jamie.Rees] - -- Name changes. [Jamie.Rees] - -- All Sln changes. [tidusjar] - -- Moved API Sln dir. [tidusjar] - -- Fixed build. [tidusjar] - -- Moved namespaces. [tidusjar] - -- Renamed zip. [tidusjar] - -- Product name change. [tidusjar] - - -## v1.10.1 (2016-12-17) - -### **Fixes** - -- #788 fixed! [tidusjar] - -- Fixed #788 and #791. [tidusjar] - -- #399 #398. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Small refactorings. [Jamie.Rees] - -- #782. [Jamie.Rees] - -- #785. [Jamie.Rees] - - -## v1.10.0 (2016-12-15) - -### **New Features** - -- Update README.md. [Jamie] - -- Added optional launch args for the auto updater. [Jamie.Rees] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update _Navbar.cshtml. [Jamie] - -- Update README.md. [Jamie] - -- Update _Navbar.cshtml. [Jamie] - -- Update README.md. [Jamie] - -- Added a new permission to bypass the request limit. [Jamie.Rees] - -- Update Version1100.cs. [SuperPotatoMen] - -- Added logging around the Newsletter #717. [Jamie.Rees] - -- Added missing migration. [tidusjar] - -- Added loading spinner. [Jamie.Rees] - -- Update UI.resx. [SuperPotatoMen] - -- Update Version1100.cs. [Jamie] - -- Update ISSUE_TEMPLATE.md. [SuperPotatoMen] - -### **Fixes** - -- Fixed an issue where the HTML in the newsletter was incorrect. [Jamie.Rees] - -- Fixed #201. [Jamie.Rees] - -- Fixed #720 and added better error handling around the migrations. [Jamie.Rees] - -- Fixed #769. [Jamie.Rees] - -- Write out the actual file version. [Jamie.Rees] - -- Error checking around GA. [TidusJar] - -- Fixed #761. [tidusjar] - -- Fixed #749 Fixed an issue where we were adding the read only permission when creating the admin. [tidusjar] - -- Fixed #757. [tidusjar] - -- Fixes around the server admin #754. [Jamie.Rees] - -- Removed the trace option from the UI, it is only accessible when appending the url with "?developer" #753. [Jamie.Rees] - -- Fixed an issue where the admin could not be updated. [Jamie.Rees] - -- Fixed #745. [Jamie.Rees] - -- Fixed #748. [Jamie.Rees] - -- Workaround for #748. [SuperPotatoMen] - -- Another attempt to fix #717. [tidusjar] - -- Fixed #744. [Jamie.Rees] - -- Tidied up the warnings. [Jamie.Rees] - -- Small bit of analytics. [Jamie.Rees] - -- Removed the whitelist. [Jamie.Rees] - -- Should fix #696 Fixed an issue with the scheduled jobs where it could use a different trigger if the order between the schedules and the triggers were in different positions in the array... Stupid me, ordering both arrays by the name now. [tidusjar] - -- Some better null object handling #731. [Jamie.Rees] - -- Small tweaks. [Jamie.Rees] - -- Fixed #728. [Jamie.Rees] - -- Fixed #727. [Jamie.Rees] - -- Fixed admin redirect issue. [tidusjar] - -- Fixed #718. [tidusjar] - -- Lots of small fixes and tweaks. [Jamie.Rees] - -- Tidied up some of the angular code, split the UI into it's own directives for easier maintainability. [Jamie.Rees] - -- Small tweaks to the Request Page. [Jamie.Rees] - -- Reverted the PR that may have caused #619. [Jamie.Rees] - -- Some small tweaks around #218 Just added the link to the settings and some angular improvements. [Jamie.Rees] - -- Test. [Jamie.Rees] - -- Attempt at fixing #686. [Jamie.Rees] - -- Finished #707. [Jamie.Rees] - -- Fixed #704. [Jamie.Rees] - -- Fixed #705. [Jamie.Rees] - -- Fixed #706. [Jamie.Rees] - -- #547. [Jamie.Rees] - -- Default tabs #304. [Jamie.Rees] - -- Fixed #703. [Jamie.Rees] - -- #233. [Jamie.Rees] - -- Done #678. [Jamie.Rees] - -- Fixed #670. [Jamie.Rees] - -- #456 Update all the requests when we identify that the username changes. [Jamie.Rees] - -- More user management. [Jamie.Rees] - -- #218. [Jamie.Rees] - -- Fixed build. [tidusjar] - -- Implimented the features #218. [tidusjar] - -- Use the user alias everywhere if it is set #218. [tidusjar] - -- Implimented auto approve permissions #218. [tidusjar] - -- A. [tidusjar] - -- Fixed an IOC issue. [tidusjar] - -- Fixed the issue with user management, needed to implement our own authentication provider. [Jamie.Rees] - -- Small changes including #666. [Jamie.Rees] - -- Done #679. [tidusjar] - -- Reduce the retry time. [Jamie.Rees] - -- Remove all references to the claims. [Jamie.Rees] - -- Lots of fixed and stuff. [Jamie.Rees] - -- Fixed potential crash #683. [Jamie.Rees] - -- Fixed #681. [Jamie.Rees] - -- More user management. [Jamie.Rees] - -- Finishing off the user management page #218 #359 #195. [Jamie.Rees] - -- Finished #646 and fixed #664. [Jamie.Rees] - -- Started on #646. Fixed #657. [Jamie.Rees] - -- Fixed #665. [Jamie.Rees] - -- Migrate users. [TidusJar] - -- Fixed build. [Jamie.Rees] - -- Convert the for to foreach for better readability. Still need to rework this area. [Jamie.Rees] - -- Final Tweaks #483. [Jamie.Rees] - -- Finished #483. [Jamie.Rees] - -- Finished the queue #483. [Jamie.Rees] - -- Started on the queue for requests #483 TV Requests with missing information has been completed. [Jamie.Rees] - -- Fixed build. [Jamie.Rees] - -- Finished the notification for the fault queue. [Jamie.Rees] - -- Finished #556. [Jamie.Rees] - -- Finished #633 (First part of the queuing) [Jamie.Rees] - -- Finished #659 #236 has been modified slightly. Needs testing on Different systems. [Jamie.Rees] - -- Almost finished #659. [Jamie.Rees] - -- Started on #483. [Jamie.Rees] - -- #544. [Jamie.Rees] - -- Fixed #656 and more work on #218. [Jamie.Rees] - -- Fixed some issues with the user management work. [TidusJar] - -- Fixed build issue. [TidusJar] - -- User perms. [Jamie.Rees] - - -## v1.9.7 (2016-11-02) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Potential fix for #629. [TidusJar] - -- Fixed an issue to stop blatting over the base url. [tidusjar] - -- Fixed #643. [TidusJar] - -- Fixed #622. [TidusJar] - - -## v1.9.6 (2016-10-28) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fixed #586. [Jamie.Rees] - -- Fixed #622. [Jamie.Rees] - -- Fixed #621. [Jamie.Rees] - - -## v1.9.5 (2016-10-27) - -### **New Features** - -- Added our own custom migrations, a lot easier to migrate DB versions now. [tidusjar] - -### **Fixes** - -- Bump version. [Jamie.Rees] - -- Small bit of work on the user claims. [Jamie.Rees] - -- Fix #612 again. [Jamie.Rees] - -- User management styling. [Jamie.Rees] - -- Fixed #608 and some other small stuff. [tidusjar] - -- More user mapping. [tidusjar] - -- Fixed #615. [tidusjar] - -- Fixed #610. [tidusjar] - -- User management stuff. [Jamie.Rees] - -- User management work. [Jamie.Rees] - -- Revert the TVSender to use the old code. [Jamie.Rees] - -- Fixed the view issue. [tidusjar] - -- S582: admin improvements part 2. [Jim MacKenzie] - -- Fix #612. [Jamie.Rees] - -- User management, migration and newsletter. [Jamie.Rees] - -- #602 recently added improvements. [tidusjar] - -- Revert "Sorting out the current state of migrations" [Jamie.Rees] - -- Sorting out the current state of migrations. [Jamie.Rees] - -- Marked as obsolete. [Jim MacKenzie] - -- Migration setup. [Jim MacKenzie] - -- Removed extra line breaks. [Jim MacKenzie] - -- Moved Newsletter Settings to its own page. [Jim MacKenzie] - -- Reverted TMDB package. [Jamie.Rees] - -- Remove DB Option. [Jamie.Rees] - -- Upgrade the movie DB package and fixed #370 To fix this I had to make another API call... It slows down the search... [tidusjar] - -- Lots of small fixes including #475. [tidusjar] - -- A better fix for #587. [tidusjar] - -- Fixed #553. [tidusjar] - -- #601. [Jamie.Rees] - -- More rework to use the Plex DB. [Jamie.Rees] - -- More work around using the PlexDatabase. [Jamie.Rees] - -- Plex DB. [Jamie.Rees] - -- Allow to process even know we had an error #578. [Jamie.Rees] - -- Fix boostrapper-datetimepicker imports (#586) [David Torosyan] - -- Potential work around for #587. [tidusjar] - -- Fixed #589. [tidusjar] - - -## v1.9.4 (2016-10-10) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added Paypalme options, no UI yet (#568) [Jim MacKenize] - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -### **Fixes** - -- Reverted. [tidusjar] - -- Make sure it's enabled before sending the recently added. [tidusjar] - -- Moved the HR inside the table for TV Shows. [Jamie.Rees] - -- FIXED!!!!! YES BITCH! #550. [tidusjar] - -- Moved the horizontal rules inside the table row. [tidusjar] - - -## v1.9.3 (2016-10-09) - -### **New Features** - -- Added properties to disable tv requests for specific episodes or seasons and wired up to admin settings. [Matt McHughes] - -- Added different sonarr search commands. [tidusjar] - -### **Fixes** - -- Fixed #515. [tidusjar] - -- Fixed #561 and a small bit of work on #569. [tidusjar] - -- #569. [tidusjar] - -- Fixed case typo. [Matt McHughes] - -- Finished wiring tv request settings to tv search. [Matt McHughes] - -- WIP hide tv request options based on admin settings. [Matt McHughes] - -- Set meta charset to be utf-8. [Madeleine Schönemann] - -- F#552: updated labels text. [Jim MacKenize] - -- F#552: Re-design lables. [Jim MacKenzie] - -- Last correction.. Now the translation is ready to be used. [Michael Reber] - -- Forgot to correct two incorrect translations. [Michael Reber] - -- Correction of the German translation. [Michael Reber] - -- Notification improvements. [tidusjar] - -- #515. [tidusjar] - - -## v1.9.2 (2016-09-18) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update CouchPotatoCacher.cs. [Jamie] - -- Added some error handing around the GetMovie area #517. [tidusjar] - -- Added a version endpoint in "/api/version" #529. [tidusjar] - -### **Fixes** - -- Trying to fix the auto CP. [tidusjar] - -- Increase the notice message text box #527. [tidusjar] - -- #536 this should fix notification settings when it is being unsubscribed when testing. [tidusjar] - -- Improved how the TV search looks and feels. [tidusjar] - -- Fix for reverse proxy when using the wizard. [Devin Buhl] - -- Fixed #532. [tidusjar] - -- This should fix some issues with the episode requests #514. [tidusjar] - -- Small changes around existing series. [tidusjar] - -- Fixed #514 and the unit tests. [tidusjar] - -- If there is a bad password when changing it, we now inform the user. [tidusjar] - -- When logging out as admin remove the username from the session. [tidusjar] - -- Sorted out some of the UI for #18. [tidusjar] - -- Finished #18. [tidusjar] - - -## v1.9.1 (2016-08-30) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added french to the navbar. [tidusjar] - -- Changed the way we use the setTimeout function. Should fix #403 #491 #492. [tidusjar] - -- Change the redirection to use a relative uri redirect #473. [tidusjar] - -### **Fixes** - -- Fixed tests. [tidusjar] - -- Fixed #491 and added more logging around the email messages under the Info level. [tidusjar] - -- Finished #415. [tidusjar] - -- Fixed an issue where there were some JS errors on the landing page settings and stopped us being redirected to the login sometimes as an admin. [tidusjar] - -- Fixed #480. [tidusjar] - -- User management. [tidusjar] - -- Fixed #505. [tidusjar] - -- Append the application version to the end of our JS/CSS files. [tidusjar] - -- Fixed issue #487. [tidusjar] - -- Remove the datetime picker css from the main assets block and only load it on the pages it needs. #493. [tidusjar] - -- Redirect to search if we are already logged in #488. [tidusjar] - -- Fixed build. [tidusjar] - -- Fixed an issue where you could set the base url as requests #479. [tidusjar] - -- Working on the beta releases page and also the user management. [tidusjar] - -- User work. [tidusjar] - - -## v1.9.0 (2016-08-18) - -### **New Features** - -- Update the availability checker to search for TV Episodes. [tidusjar] - -- Changed the no TVMazeid message. [tidusjar] - -- Added an option to disable/enable the Plex episode cacher. [tidusjar] - -- Updated the episode cacher to have a minimum of 11 hours before it runs again. [tidusjar] - -- Added some useful analytical infomation around the wizard. [tidusjar] - -- Updated the German translations #402. [tidusjar] - -- Added some code to shrink the DB. reworked the search to speed it up. [tidusjar] - -- Change to use the GrandparentTitle rather than the thumbnail.... facepalm. [tidusjar] - -- Change the interval to hours! [tidusjar] - -- Added the transaction back into the DB. Do not run the episode cacher if it's been run in the last hour. [tidusjar] - -- Added logging. [tidusjar] - -- Changed the query slightly. [tidusjar] - -- Updated Newtonsoft.Json, Autofixture, Nlog and Dapper packages. [tidusjar] - -- Added the Sonarr check for episodes #254. [tidusjar] - -- Added unit tests. [tidusjar] - -- Added #436. [tidusjar] - -- Update build no. [tidusjar] - -- Updated translations for #402. [tidusjar] - -- Added a beta module. [tidusjar] - -- Added a custom debug root path provider, this means we do not have to recompile the views every time we make a view change. [tidusjar] - -- Update .gitignore. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added tests for the string hash. [tidusjar] - -- Added code to request the api key for CouchPotato. [tidusjar] - -- Added the file version to the layout. [tidusjar] - -- Update appveyor.yml. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added automation tests. [tidusjar] - -- Update appveyor.yml. [Jamie] - -- Updated packages. [tidusjar] - -- Updated Polly. [tidusjar] - -- Updated Fr, IT and NL translations #402. [tidusjar] - -- Changed the way the donate button works for #414. [tidusjar] - -- Added MediatR. [tidusjar] - -### **Fixes** - -- User management stuff. [tidusjar] - -- Fixed! [tidusjar] - -- Small amount of work on the user management. [tidusjar] - -- Fixed #466. [tidusjar] - -- Fixes. [tidusjar] - -- Made the episode request better. [tidusjar] - -- Removed commented out tests. [tidusjar] - -- Fixed the bad test after the merge. [tidusjar] - -- Reworked #466. [tidusjar] - -- More unit tests around the login and also the core Plex Checker. [tidusjar] - -- Potentially fixed the issue where we were requesting everything that was also available now. [tidusjar] - -- This should fix #466. [tidusjar] - -- Attempt at fixing a potential bug found from #466. [tidusjar] - -- #464 fixed. [tidusjar] - -- Fixed the build. [tidusjar] - -- Small improvements to the wizard. [tidusjar] - -- Always set the wizard to be true when editing the Plex Requests settings (Since the flag is not in the UI, a bool defaults to false). [tidusjar] - -- Tiny bit of more info. [tidusjar] - -- Finished #459. [tidusjar] - -- #459 is almost done. [tidusjar] - -- Modified the episode modal so that we are now resetting the button after a request. [tidusjar] - -- Commented out the transaction for now to debug it. [tidusjar] - -- Since we are multithreading, we should use a threadsafe type to store the episodes to prevent any threading or race conditions. [tidusjar] - -- Wrapped the bulk insert inside a transaction. [tidusjar] - -- Made the episode check parallel. [tidusjar] - -- Log out the GUID causing the issue. [tidusjar] - -- Fixed another test. [tidusjar] - -- Fixed tests. [tidusjar] - -- Got mostly everything working for #254 Ready for testing. [tidusjar] - -- Fixed issue with saving to db. [tidusjar] - -- Need to work out why the cacher is not working and where the datatype mismatch is. [tidusjar] - -- Don't delete first. [tidusjar] - -- Fix the log path issue #451. [tidusjar] - -- Dump an item. [tidusjar] - -- Small change with the return value in the batch insert. [tidusjar] - -- #254 Removed the cache, we are now storing the plex information into the database. [tidusjar] - -- Small change in the episode saver. [tidusjar] - -- Some small tweaks to improve the memory alloc. [tidusjar] - -- Short circuit when Plex hasn't been setup. Added Miniprofiler. [tidusjar] - -- Consolidate newtonsoft.json packages. [tidusjar] - -- Some performance improvements around the new TV stuff. [tidusjar] - -- Reworked the cacher, fixed the memory leak. No more logging within tight loops. [tidusjar] - -- Another null check. [tidusjar] - -- Some more changes. [tidusjar] - -- Some error handling. [tidusjar] - -- Check if the sonarr ep is monitored. [tidusjar] - -- Some logging. [tidusjar] - -- Small changes, we will actually see the episode cacher on the scheduled jobs page now. [tidusjar] - -- Work on the UI to show what episodes have been requested #254. [tidusjar] - -- Small fix. [tidusjar] - -- Fix the api change in #450. [tidusjar] - -- #254. [tidusjar] - -- Workaround for #440. [tidusjar] - -- Async async async improvements. [tidusjar] - -- Finished #266 Added a new cacher job to cache all episodes in Plex. [tidusjar] - -- Fixed #442. [tidusjar] - -- #254. [tidusjar] - -- Work around the sonarr bug #254. [tidusjar] - -- #254 having an issue with Sonarr. [tidusjar] - -- Small bit of work on #266. [tidusjar] - -- #254. [tidusjar] - -- Precheck and disable the episode boxes if we already have requested it. TODO check sonarr to see if it's already there. #254. [tidusjar] - -- Fixed broken build. [tidusjar] - -- More work for #254. [tidusjar] - -- More work on #254. [tidusjar] - -- Fixed the bug in #438 and added unit tests to make so we dont break it in the future. [tidusjar] - -- Some reason we had dupe translations. [tidusjar] - -- Rename SubDir to Base Url. [tidusjar] - -- Fix the exception in #440. [tidusjar] - -- Reworking the login page for #426. [tidusjar] - -- Fixed #438. [tidusjar] - -- Finished the auth stuff. [tidusjar] - -- Finished up the SMTP side of #429. [tidusjar] - -- #428 Added a message when the we cannot get a TVMaze ID. [tidusjar] - -- #254 MOSTLY DONE! At last, this took a while. [tidusjar] - -- Removed the other rootpath provider. [TidusJar] - -- Removed the other rootpath provider. [TidusJar] - -- Removed the other rootpath provider. [TidusJar] - -- Should fix #429. [TidusJar] - -- Done #135 We are including the application version number in the directory. [tidusjar] - -- #387 trim the spaces from the api key. Tidied up the setting models a bit. [tidusjar] - -- Wrapped the repo to catch Sqlite corrupt messages. [tidusjar] - -- Frontend and tv episodes api work for #254. [tidusjar] - -- #424. [tidusjar] - -- #359. [tidusjar] - -- Moved the plex auth token to the plex settings where it should belong. [tidusjar] - -- Small changes around the user management. [tidusjar] - -- Missed brace. [tidusjar] - -- Removed. [tidusjar] - -- Fixed issues from the merge. [tidusjar] - -- Stupid &$(*£ merge. [tidusjar] - -- Angular. [tidusjar] - -- Reworked the custom notifications... again. Need to figure out how to find the view to the model. [tidusjar] - -- Fixed #417. [tidusjar] - -- Removed NinjectConventions, we hadn't started to use it anyway. [tidusjar] - -- Fixed the way we will be using custom messages. [tidusjar] - -- Test checkin. [tidusjar] - -- Better handling for #388. [tidusjar] - -- Fixed #412. [tidusjar] - -- Fixed #413. [tidusjar] - -- Fixed #409. [tidusjar] - -- Trycatch around the availbility checker. [tidusjar] - -- WIP on notification resolver. [tidusjar] - -- Tidy. [tidusjar] - -- Plugged in MediatR. [tidusjar] - -- Moved over to using Ninject. [tidusjar] - - -## v1.8.4 (2016-06-30) - -### **Fixes** - -- Fixed the bug where we were auto approving everything. Added French language into the navigation bar. [tidusjar] - - -## v1.8.3 (2016-06-29) - -### **New Features** - -- Update README.md. [Jamie] - -- Update appveyor.yml. [Jamie] - -- Added some of the backend bits for #182. [tidusjar] - -- Updates for #243. [tidusjar] - -- Added Dutch language #243. [tidusjar] - -- Added languages #243. [tidusjar] - -- Added logging #350. [tidusjar] - -### **Fixes** - -- Small changes. [tidusjar] - -- Allow html in the notice message. [tidusjar] - -- Some more unit tests around the NotificationMessageResolver. [tidusjar] - -- Fixed a timing bug found the in build. Note, when working with time differences use TotalDays. [tidusjar] - -- More translations on the search page (Mainly the notification messages) #243. [tidusjar] - -- Fixed some warnings. [tidusjar] - -- CodeCleanup. [tidusjar] - -- Fixed a bit of a stupid bug in the resetter and added unit tests around it to make sure this never happens again. [tidusjar] - -- Fixed an issue where we didn't provide the correct response when clearing the logs. [tidusjar] - -- Made it so users that are in the whitelist do not have a request limit. [tidusjar] - -- Made it so the request limit doesn't apply to admin users. [tidusjar] - -- Fixed where a user could see the delete button on the issues page. [tidusjar] - -- Fixed some small issues and improved the navbar. [tidusjar] - -- Translated the Requested page #243. [tidusjar] - -- Finished #337. [tidusjar] - -- Some analytics. [tidusjar] - -- More translations for #243 and welcome text for #293. [tidusjar] - -- Small bit of work for #359. [tidusjar] - -- Finished #6. [tidusjar] - -- Analytics and fixes. [tidusjar] - -- Translated the search page #243. [tidusjar] - -- Implemented the different languages and added the ability to change cultures. #243. [tidusjar] - -- Started #243. [tidusjar] - -- Fixed #364. [tidusjar] - -- Some more useful analytical information. [tidusjar] - -- Generic try catch to fix #350. [tidusjar] - -- Slight changes, moved the donate button. [tidusjar] - -- Potential fix for #350. [tidusjar] - -- Better way of obtaining clean enum string. [Drewster727] - -- Fixed #362. [tidusjar] - - -## v1.8.2 (2016-06-22) - -### **New Features** - -- Update readme. [tidusjar] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fixed a circular reference issue. [tidusjar] - -- Small changes around how we work with custom events in the analytics. [tidusjar] - -- Fixed #353 #354 #355. [tidusjar] - -- Null provider check for movies. [Drewster727] - -- Show request type in notifications #346 and fix an issue from previous commit for #345. [Drewster727] - -- Add an option to stop sending notifications for requests that don't require approval #345. [Drewster727] - - -## v1.8.1 (2016-06-21) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Fix obj ref error when scheduler runs (ProviderId is null?) [Drewster727] - -- Fix logic for obtaining a sonarr quality profile #340. [Drewster727] - - -## v1.8.0 (2016-06-21) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the new advanced search into the search page too. [tidusjar] - -- Change the way we configure the IoC container in the bootstrapper, we are registering all the concrete instances on application start rather than on each web request. This should increase the performance per HTTP request. [tidusjar] - -- Updated nlog and fixed #295. [tidusjar] - -### **Fixes** - -- Workaround for #334. [Drewster727] - -- Create .gitattributes. [Jamie] - -- Fixes to the issues. [tidusjar] - -- Set the defaults for the landing page. [tidusjar] - -- Revert branch to 664dae2. [tidusjar] - -- Some unit tests for the issues. [tidusjar] - -- Tidied up the bootstrapper. [tidusjar] - -- Fix up landing page UI. [Drewster727] - -- Fixed CSS issue with the top arrow in the Plex theme. [tidusjar] - -- Small changes. [tidusjar] - -- Done #318. [tidusjar] - -- Fixed tests. [tidusjar] - -- #298 added some tests for the landing page. [tidusjar] - -- We are now only keeping the latest 1000 log records in the database. Delete everything else. [tidusjar] - -- Some analytic stuff. [tidusjar] - -- Capture the TVDBID when requesting. [tidusjar] - -- Attempting to improve #219. [tidusjar] - -- Just some more async changes. [tidusjar] - -- Small changes. [tidusjar] - -- More work on #298. Everything wired up. [tidusjar] - -- Fixed the issue on the landing page #298. [tidusjar] - -- #298 moved the content to the left a bit. [tidusjar] - -- Styling for #298 done, just need to wire up the model and do the actual status check. [tidusjar] - -- Bumped up the version number. [tidusjar] - -- Removed some DumpJson() from the trace logs. [tidusjar] - -- Small ui fix (100% width user/password fields to improve mobile experience) [Drewster727] - -- Landing page stuff #298. [tidusjar] - -- Datepicker UI fixes + small landing page UI fix. [Drewster727] - -- Removed a change that shoudn't have been commited. [tidusjar] - -- Fixed tests. [tidusjar] - -- More work for #298. [tidusjar] - -- #273 added for only available content on the search. [tidusjar] - -- Fixed #303 Looks like there was some incorrect business logic. [tidusjar] - -- Most of #273 done. [tidusjar] - -- Settings done for #298. [tidusjar] - -- Started #298. [tidusjar] - -- A crap tonne of work on #273. [tidusjar] - -- More work on #273. [tidusjar] - -- Reduced kept logs for 2 days. [tidusjar] - -- Fixed #300. [tidusjar] - -- #273. [tidusjar] - -- Fixed a bug with some users with the CP profiles. [tidusjar] - -- #273. [tidusjar] - -- Done the same for TV. [tidusjar] - -- Fixes #296. [tidusjar] - -- More for #273. [tidusjar] - -- Small changes. [tidusjar] - -- Revert "Small changes" [tidusjar] - -- Small changes. [tidusjar] - -- Finished #221 and added more async #278. [tidusjar] - -- Spelling mistake in the html! this fixes #264. [tidusjar] - -- More work on #273. [tidusjar] - -- Fixed #210. [tidusjar] - -- Started #273. [tidusjar] - - -## v1.7.5 (2016-05-29) - -### **New Features** - -- Update preview. [Jamie] - -- Updated dapper.contrib. Looks like there was a bug in the async methods. [tidusjar] - -- Updater wouldn't work when running a reverse proxy #236. [tidusjar] - -### **Fixes** - -- Bump build ver. [tidusjar] - -- Use HTTPS for the poster images, so there aren't any mixed content warnings when serving the application via an HTTPS reverse proxy. [Sean Callinan] - -- Removed static declarations. [tidusjar] - -- Fixed styling on modal. [tidusjar] - -- Made the search page all async goodness #278. [tidusjar] - -- Made the request module async #278. [tidusjar] - -- Started some dynamic scrolling. [tidusjar] - -- Stop dumping out the settings to the log. [tidusjar] - -- Made more async goodness. [tidusjar] - -- Made some of the searching async #278. [tidusjar] - -- Fixed #277. [tidusjar] - -- Reworked some tests. [tidusjar] - -- #26q make the auth users list taller. [Drewster727] - -- Fix 404 error. [Drewster727] - -- #262 make the auth users list taller. [Drewster727] - -- #221 delete requests per category. [Drewster727] - -- #256 #237 UI Improvements and consolidation. [Drewster727] - -- Fixed a bug in the user notification where if an admin wants to be notified they wouldn't be. [tidusjar] - -- Set the admin to have all claims. [tidusjar] - -- Fix null exception possibility in cp/sickrage cacher classes. [Drewster727] - -- Fixed #244. [tidusjar] - -- Fixed #240. [tidusjar] - -- Fixed #270. [tidusjar] - -- Fixed an issue where if you have only 1 plex friend it would not show in the list. [tidusjar] - - -## v1.7.4 (2016-05-25) - -### **New Features** - -- Update README.md. [Jamie] - -### **Fixes** - -- Fixed #252. [tidusjar] - -- Fixed #428. [tidusjar] - -- Version bump. [tidusjar] - -- Fixed tests. [tidusjar] - -- Fully fixed #239. [tidusjar] - -- We wan't updating the DB schema. [tidusjar] - - -## v1.7.3 (2016-05-25) - -### **Fixes** - -- Fixed the release build issue where we could not access the settings #239. [tidusjar] - - -## v1.7.2 (2016-05-25) - -### **Fixes** - -- Fixed a small bug where an exception would get thrown. [tidusjar] - -- Build version bump. [tidusjar] - -- Cleanup. [tidusjar] - -- Typo. [tidusjar] - -- Fixed #241. [tidusjar] - -- Fixed #239. [tidusjar] - -- Fixed #238. [tidusjar] - -- Small UI tweaks/improvements. [Drewster727] - - -## v1.7.1 (2016-05-24) - -### **New Features** - -- Update version. [tidusjar] - -### **Fixes** - -- Fixed an issue with the auth page when running with a reverse proxy. [tidusjar] - - -## v1.7 (2016-05-24) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added the ability to get the apikey from the api if you provide a correct username and password. Added more unit tests Added the ability to change a users password using the api refactored the Usermapper and made it unit testsable. [tidusjar] - -- Update. [tidusjar] - -- Added in an audit table. Since we are now allowing multiple users to change and modify things we need to audit this. [TidusJar] - -- Added the updater to the soloution and did a bit of starting code. [TidusJar] - -- Updated the claims so we can support more users. Added a user management section (not yet complete) Added the api to the solution and a api key in the settings (currently only gets the requests). [TidusJar] - -- Updated packages. [TidusJar] - -- Added a retry handler into the solution. We can now retry failed api requests. [TidusJar] - -- Update README.md. [Jamie] - -- Added Released propety to RequestViewModel. Added Released filter to the Requests page. [Chris Lees] - -- Added #27 to albums. [tidusjar] - -- Added the actual notification part of #27. [tidusjar] - -- Added the missing baseurl bit on the login page for #72. [tidusjar] - -- Added the 'enable user notifications' to the email settings view and model. [tidusjar] - -- Update README.md. [Jamie] - -### **Fixes** - -- Remove pointless test, change the default theme and fix a small bug. [tidusjar] - -- Fixed api. [tidusjar] - -- Finished #26. [tidusjar] - -- Plex theme. [tidusjar] - -- Implimented a theme changer, waiting for the Plex theme. [tidusjar] - -- Finished #222 #205. [tidusjar] - -- Started working on #26. [tidusjar] - -- Undid some small changes that was checked in by accident. [tidusjar] - -- #164 has been resolved. [tidusjar] - -- Resolved #224 , Removed the 'SSL' option from the email notification settings. We will now use the correct secure socket options (SSL/TLS) for your email host. [tidusjar] - -- Small changes. [tidusjar] - -- #27 fully finished. [tidusjar] - -- Fixed #215. [tidusjar] - -- Using Mailkit to fix #204. [tidusjar] - -- Color. [tidusjar] - -- Fully finished #27 just need to test it! [tidusjar] - -- Fixed test. [tidusjar] - -- Styling for #27. [tidusjar] - -- I think the auto updater is finished! #29. [tidusjar] - -- I think we have finished the main bulk of the auto updater #29. [tidusjar] - -- #222 #205 more ! Started getting the settings out. [tidusjar] - -- Removed the service locator from the base classes and added in some Api tests added all the tests back in! [tidusjar] - -- More work on the api and documentation #222 #205. [tidusjar] - -- Started documenting the API we now have swagger under ~/apidocs #222 #205. [tidusjar] - -- Api work for #205 Refactored how we check if the user has a valid api key Added POST request, PUT and DELTE. [tidusjar] - -- First pass of the updater working. #29. [tidusjar] - -- Removed SIGHUP from the termination list #220. [tidusjar] - -- Fixed. [tidusjar] - -- Missing. [tidusjar] - -- Missed out a file. [TidusJar] - -- And some more... [TidusJar] - -- Missed some files. [TidusJar] - -- A bit more work on switching to using user claims so we can support multiple users. [TidusJar] - -- Made the store backup clean up some of the older backups (> 7 days). [TidusJar] - -- More work on the user management. [TidusJar] - -- - Notifications will no longer be send to the admins if they request something. - Looks like we missed out adding the notifications to Music requests, so I added that in. [TidusJar] - -- - Improved the RetryHandler. - Made the tester buttons on the settings pages a bit more robust and added an indication when it's testing (spinner) [TidusJar] - -- Packages. [TidusJar] - -- Nm, [TidusJar] - -- Downgraded packages. [TidusJar] - -- Better handling for #202. [TidusJar] - -- Finished #208 and #202. [TidusJar] - -- This should help #202. [TidusJar] - -- Resolved #209. [TidusJar] - -- Finished #209. [TidusJar] - -- Slight adjustments to #189. [tidusjar] - -- - Added a visual indication on the UI to tell the admin there is a update available. - We are now also recording the last scheduled run in the database. [tidusjar] - -- Did the login bit on #185. [tidusjar] - -- Finished #186. [tidusjar] - -- Fixed #185. [tidusjar] - -- Fixed issue in #27 with albums. [tidusjar] - -- #27 added TV Search to the notification. [tidusjar] - -- Fixed bug. [tidusjar] - -- More work on #27 Added a new notify button to the search UI (Needs styling). Also fixed a bug where if the user could only see their own requests, if they search for something that has been requested, it will show as requested. [tidusjar] - -- Improved the startup of the application. We now properaly parse any args passed into the console. [tidusjar] - -- Additional cacher error handling + don't bother checking the requests when we don't get data back from plex. [Drewster727] - -- Remove old migration code and added new migration code. [tidusjar] - -- Stop the Cachers from bombing out when the response from the 3rd party api returns an exception or invalid response. #171. [tidusjar] - -- Increase the scheduler cache timeframe to avoid losing cache when the remote api endpoints go offline (due to a reboot or some other reason) -- if they're online, the cache will get refreshed every 10 minutes like normal. [Drewster727] - -- Fix the cacher by adding locking + extra logging in the plex checker + use a const key for scheduler caching time. [Drewster727] - -- Small changes. [tidusjar] - -- Switched out the schedulers, this seems to be a better implimentation to the previous and is easier to add new "jobs" in. [tidusjar] - -- Fixed #168. [tidusjar] - -- Fixed #162. [tidusjar] - -- Fix saving the log level. [Drewster727] - -- Set the max json length (fixes large json response errors) [Drewster727] - - -## v1.6.1 (2016-04-16) - -### **New Features** - -- Update README.md. [Jamie] - -- Added a url base. [tidusjar] - -- Change default logging. [tidusjar] - -- Added logging around SickRage. [tidusjar] - -### **Fixes** - -- Bump up the version number ready for the release. [tidusjar] - -- BaseUrl is finally finished! #72. [tidusjar] - -- #72 Login page done. [tidusjar] - -- More changes for the urlbase #72. [tidusjar] - -- Done the auth, cp, logs and sidebar for #72. [tidusjar] - -- Add an extra check when determining if a tv show is already available (also check if it starts with the show name returned from the tv db) [Drewster727] - -- Cache plex library data regardless of whether we have requests in the database or not. [Drewster727] - -- By default don't use a url base. [tidusjar] - -- Return empty array when obtaining queued IDs in sickrage cacher. [Drewster727] - -- Fixed a small bug in the SR cacher. [tidusjar] - -- Fixed when we do not have a base. [tidusjar] - -- More changes for #72. [tidusjar] - -- Fixed exception and all areas will now use the base url #72. [tidusjar] - -- Removed the test code from #72. [tidusjar] - -- Commented out the unit tests as they need to be reworked now. [tidusjar] - -- Finally fixed #72. [tidusjar] - -- Remove test code from plex api GetLibrary method. [Drewster727] - -- Finished up the caching TODO's. [tidusjar] - -- Kick off the schedulers once the web app has started (fixes api errors on start) [Drewster727] - -- Converted the UI back down to .NET 4.5.2. [tidusjar] - -- Fixed #154. [tidusjar] - -- Revert everything (except PlexRequests.UI) back to .NET 4.5.2 -- fixes incompatibilities with the latest version of mono (4.2.3.4) -- fixes notifications not working #152 #147 #141. [Drewster727] - -- #150 start caching plex media as well. refactored the availability checker. NEEDS TESTING. also, we need to make the Requests hit the plex api directly rather than hitting the cache as it does now. [Drewster727] - -- #150 split out the cache subscriptions to make sure they subscribe properly. [Drewster727] - -- #150 sonarr/sickrage cache checking. sickrage has a couple small items left. [Drewster727] - -- Fixed args. [tidusjar] - -- Fixed. [tidusjar] - -- Made the base better. [tidusjar] - -- Remove couchpotato api test code. [Drewster727] - -- Start the initial couchpotato cache call on a separate thread to keep the startup process quick. [Drewster727] - -- Add csproj with file changes from previous commit. [Drewster727] - -- Cache the couchpotato wanted list, update it on an interval, and use it to determine if a movie has been queued already. [Drewster727] - -- I think i've fixed an issue where SickRage reports Show not found. [tidusjar] - -- Set the default log level to info. #141. [tidusjar] - -- #125 refactor async task logic to work with mono. [Drewster727] - -- Fix search spinner sticking around after clearing search text + make the "Requested" and "Available" indicators in the search page different colors. [Drewster727] - -- #125 start indicating in the results if an item is already requested or available. [Drewster727] - -- #145 firefox css dsplay issue. [Drewster727] - -- Fixes for sonarr, we now display the error messages back to the user. [tidusjar] - -- Fixed #144. [tidusjar] - - -## v1.6.0 (2016-04-06) - -### **New Features** - -- Changed the build number. [tidusjar] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Update README.md. [Drew] - -- Changed the title to a contains but the artist still must match, [tidusjar] - -- Added unit tests to cover the new changes to the availability checker. [tidusjar] - -- Added the music check in the Plex Checker. [tidusjar] - -- Changed around the startup so we cache the profiles after the DB has been created. [tidusjar] - -- Updated where we update the request blobs schema change. [tidusjar] - -- Update SearchModule.cs. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Change the new columns type. [tidusjar] - -- Added a DBSchema so we have an easier way to update the DB. [tidusjar] - -- Added an issue template. [tidusjar] - -- Update README.md. [Jamie] - -- Added back the username into the Session when the admin logs in. This means they do not have to log in twice. [tidusjar] - -- Added happy path tests for the Checker. [tidusjar] - -- Added music to the search and requests page. [tidusjar] - -- Added a scroll to the top thingy and a bit more work on headphones. [tidusjar] - -- Added some tests and fixed the issue where the DB would get created in the wrong place depending on how you launched the application. [tidusjar] - -- Added the settings page for #32. [tidusjar] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update README.md. [Drewster727] - -- Update appveyor.yml. [Jamie] - -### **Fixes** - -- Some final tweaks for #32. [tidusjar] - -- Fixed a bug where if we are the admin we didn't add the request to the db. [tidusjar] - -- Fixed an issue where we would add the Sickrage series but it would fail on adding the seasons. [tidusjar] - -- Properly account for future/past dates when humanizing with moment. [Drewster727] - -- Properly display release date on requests page. [Drewster727] - -- Add missing reference for release mode. [Drewster727] - -- #139 remove dependency and usage of humanize() - should help with cross-platform issues. start using moment.js. [Drewster727] - -- Fix selectors for music list on request page to get sorting working. [Drewster727] - -- Fixed the error #32. [tidusjar] - -- Fixed the logs page. [tidusjar] - -- Another attempt at filtering #32. [tidusjar] - -- A bit more error handling #32. [tidusjar] - -- Improved the availabilty check to include music results #32. [tidusjar] - -- Small changes for #32. [tidusjar] - -- A bit more logging for #32. [tidusjar] - -- More headphones #32 I am starting to hate headphones... Sometimes the artists and albums just randomly fail. [tidusjar] - -- #134 temporary workaround for this. [Drewster727] - -- Task.run for startup caching + fix admin module unit test failures. [Drewster727] - -- Cache injection, error handling and logging on startup, etc. [Drewster727] - -- Tweaks for #32. [tidusjar] - -- #132 auto-approve for admins. [Drewster727] - -- Finished the bulk work for Headphones. Needs testing #32. [tidusjar] - -- Made the album search 10x faster. We are now loading the images in a seperate call. #32. [tidusjar] - -- Add a reference to API Interfaces to fix the build. [tidusjar] - -- #114 start caching quality profiles. Set the cache on startup and when obtaining quality profiles in settings. [Drewster727] - -- Work for #32. [tidusjar] - -- #114 first pass at choosing quality profile when approving + focus search input by default and when switching tabs. [Drewster727] - -- #131 fix for default selected tab. [Drewster727] - -- Remove references to obsolete RequestedBy property + start setting the db schema to the app version, and check that in the future for migrations. [Drewster727] - -- Fixed async issue. [Shannon Barrett] - -- Updating SickRage api to verify Season List is up to date. [Shannon Barrett] - -- Work on showing the requests for #32. [tidusjar] - -- Got the search finished up for #32. [tidusjar] - -- Remove test/temp code in UserLoginModule. [Drewster727] - -- A bit more work on #32 started working on requesting it. The DB is a bit of an issue... [tidusjar] - -- Most of the UI work done for #32. [tidusjar] - -- Basic search working for #32. [tidusjar] - -- Mono datetime offset workaround. [Drewster727] - -- #122 store utc time in the databse + obtain timezone offset of the client upon login + offset times returned to client based on session offset. [Drewster727] - -- Method reference bug fix. [Drewster727] - -- Fix search focus z-index issue (hid suggestions options) [Drewster727] - -- Minor search UI adjustments. [Drewster727] - -- #55 first attempt at "suggestions" starting with "Comming Soon" and "In Theaters" [Drewster727] - -- #106 rename sorting options and polish the dropdown UI a bit. [Drewster727] - -- Started adding the api part for headphones #32. [tidusjar] - -- Upped the time of #123. [tidusjar] - -- First attempt at #123. [tidusjar] - -- We now do not show the text Requested By to the user, we also show a 'success' message instead of a warning when something has already been requested. [tidusjar] - -- Show a "no requests yet" message on the requests page (for each cateogory) [Drewster727] - -- Ignore items that are already available when approving in bulk, and simplify the checking + compile css. [Drewster727] - -- Add a better way to merge RequestedBy and RequestedUsers to avoid code duplication and simplify checks. [Drewster727] - -- Don't query the session as much in the modules, rely on a variable from the base class and store the username as needed. [Drewster727] - -- Show the requested by user from legacy request models. [Drewster727] - -- Only show requested by users to admins + start maintaining a list of users with each request. [Drewster727] - -- #96 fix up notification test feature. [Drewster727] - -- Fix the request page sort/approve button alignment. [Drewster727] - -- When pulling requests, set each to approved that is already available (so the UI avoids showing the approve option for already available content) [Drewster727] - -- Mono doesn't seem to have Tls1.2. Let's try TLS 1 #119. [tidusjar] - -- Specify a protocol type of TLS12. Looks like CP doesn't seem to like SSL3 (it is quite old now so understandable) #119. [tidusjar] - -- Made #85 better. [tidusjar] - -- Fixed the tests. [tidusjar] - -- Made the feedback from Sonarr better when Sonarr already has the series #85. [tidusjar] - -- An attempt to fix #108. [tidusjar] - -- Add some "no results" feedback to the searching + minor UI improvements. [Drewster727] - -- Fix notification tests. [Drewster727] - -- UI - increase icon size of nav menu (they were too small before) [Drewster727] - -- #96 Finished adding test functionality to notifications. [Drewster727] - -- #96 add the necessary back-end code to produce a test message for all notification types (still have to add the test buttons for pushbullet/pushover) [Drewster727] - -- #96 modify notifications interface/service to accept a non-type specific settings object. [Drewster727] - -- #96 Email notification test button (others to come) [Drewster727] - -- Minor UI adjustments. [Drewster727] - -- #84 provide an option in settings to resttrict users from viewing requests other than their own. [Drewster727] - -- #54 comma separated list of users who don't require approval + fix a couple request messages (include show title) [Drewster727] - -- Clean up the sorting option names. add a way to see which filter/sort is currently applied. [Drewster727] - -- Fix up the animations. seems to be related to the data-bound attribute causing the animtions not to fire on each .mix object. [Drewster727] - -- Move approve buttons to the tab content. [Drewster727] - -- Allow approving all requests by category. [Drewster727] - -- Fix up sorting on the request page. [Drewster727] - -- Add ubuntu/debian instructions. [Drewster727] - -- #86 - display movie/show title + year in request notifications. [Drewster727] - -- Show the movie/show title when requesting. [Drewster727] - - -## v1.5.2 (2016-03-26) - -### **Fixes** - -- Stoped users from spamming the request button. [tidusjar] - -- Fixed the logger no longer writing to the file. [tidusjar] - -- Fixed #97. [tidusjar] - - -## v1.5.1 (2016-03-26) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Added logs to the sidebar. I'm an idiot. [tidusjar] - -### **Fixes** - -- Approve tv shows or movies. [Drewster727] - -- Fixed a bug where if you had auto approve it wouldn't notify you. [tidusjar] - - -## v1.5.0 (2016-03-25) - -### **New Features** - -- Updated version number for release. [tidusjar] - -- Updated the logic for handling specific seasons in Sonarr and Sickrage. [Shannon Barrett] - -- Updated the readme and added some icons to the navbar. [tidusjar] - -- Added the ability to sepcify a username in the email notification settings for external MTA's. We have had to add a new option called Email Sender because of this. #78. [tidusjar] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Added a notification model to the notifiers. Added the backend work for sending a notification for an issue report #75. [tidusjar] - -- Added a subdir to CP, SickRage, Sonarr and Plex #43. [tidusjar] - -### **Fixes** - -- And again. [tidusjar] - -- Made the check actually work. [tidusjar] - -- Finished up #68 and #62. [tidusjar] - -- Finished styling on the logger for now. #59. [tidusjar] - -- Fixed #69. [tidusjar] - -- Working on getting the Sonarr component to work correctly. [Shannon Barrett] - -- Fixes issue #62. [Shannon Barrett] - -- Refactored the Notification service to how it should have really been done in the first place. [tidusjar] - -- Fixed the build. [tidusjar] - -- Finished #49. [tidusjar] - -- Finished #57. [tidusjar] - -- Small changes around the filtering. [tidusjar] - -- Finished adding pushover support. #44. [tidusjar] - -- Resolved #75. [tidusjar] - -- Include DB changes. [tidusjar] - -- Done most on #59. [tidusjar] - -- Lowercase logs folder, because you know, linux. #59. [tidusjar] - -- Adding the imdb when requesting. [tidusjar] - -- Fixed an issue where the table didn't match the model. [tidusjar] - -- Improved the status page with the suggestion from #29. [tidusjar] - -- Hooked up most of #49 Just the validation messages need to be done. [tidusjar] - -- Fixed #74 and #64. [tidusjar] - -- Resolved #70. [tidusjar] - -- Finished #71. [tidusjar] - -- Got the filter working on both movie and tv #57. [tidusjar] - -- Started #57, currently there is a bug where the TV list won't filter. [tidusjar] - - -## v1.4.1 (2016-03-20) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Update AvailabilityUpdateService.cs. [Jamie] - - -## v1.4.0 (2016-03-19) - -### **New Features** - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Update README.md. [Jamie] - -- Updated the build version ready for the next release. [tidusjar] - -- Added the api and settings page for Sickrage. Just need to do the tester and hook it up #40. [tidusjar] - -- Added the option to set a CP quality #38. [tidusjar] - -- Added the code to lookup the old requests and refresh them with new information from TVMaze. [tidusjar] - -- Update StatusCheckerTests.cs. [Jamie] - -- Update README.md. [Jamie] - -- Added TVMaze to the search. #21. [tidusjar] - -- Added migration code and cleaned up the DB. [tidusjar] - -- Updated the way we add requests. [tidusjar] - -- Updated the Dapper.Contrib package, it had a bug where it wasn't returning the correct Id from inserts. [tidusjar] - -### **Fixes** - -- This fixes #36. [tidusjar] - -- Should fix issue #36. [Shannon Barrett] - -- When we do a batch update we need to reset the cache. [tidusjar] - -- Fixed an issue where the default quality on Sickrage wouldn't work. [tidusjar] - -- Wow, that was a lot of work. - So, I have now finished #40. - Fixed a bug where we was not choosing the correct tv series (Because of TVMaze) - Fixed a bug when checking for plex titles - Fixed a bug where the wrong issue would clean on the UI (DB was correct) - Refactored how we send tv shows - And too many small changes to count. [tidusjar] - -- Fixed the new dependancy with the admin class tests. [tidusjar] - -- Back to what it was :( [tidusjar] - -- Another test for #37. [tidusjar] - -- This should fix #37. [Jamie Rees] - -- Catch the missing table exception when they have a new DB. [Jamie Rees] - -- Exploratory test for #37. [Jamie Rees] - -- Fixed #33 we now have SSL options for Sonarr and CP. [Jamie Rees] - -- Removed all the html from the new TVMaze api (for overview). Added tests to cover the html removal. updated Readme to remove TheTVDB. [Jamie Rees] - -- Fixed tests. [Jamie Rees] - -- Almost fully integrated TVMaze #21 and also improved the fix for #31. [Jamie Rees] - -- Should fix #28. [Shannon Barrett] - -- Fixed #16 and #30. [tidusjar] - -- Modified the adding of request to update the model with the added ID. [tidusjar] - -- Switched over to the new service. [tidusjar] - -- Fixed #25. [Jamie Rees] - - -## v1.3.0 (2016-03-17) - -### **New Features** - -- Added pushbullet to the sidebar. [Jamie Rees] - -- Updated build version for the next release. [Jamie Rees] - -- Updated readme link. [tidusjar] - -- Added ignore to static tests. [tidusjar] - -- Added Pushbullet notifications #8. [tidusjar] - -- Added first implimentation of the Notification Service #8 Added tests to cover the notification service. [tidusjar] - -- Added validation to the Email settings, also increased the availability checker from 2 minutes to 5. [tidusjar] - -### **Fixes** - -- Fixed #22. [Jamie Rees] - -- Started on #16, nothing is hooked up yet. [tidusjar] - -- Fixed tests. [tidusjar] - - -## v1.2.1 (2016-03-16) - -### **New Features** - -- Update Program.cs. [Jamie] - -- Update Program.cs. [Jamie] - -- Added back the reference. [tidusjar] - -### **Fixes** - -- Removed the email notification settings from the settings (for release 1.2.1) [Jamie Rees] - -- Fixed. [Jamie Rees] - -- Resolved #10. [tidusjar] - - -## v1.2.0 (2016-03-15) - -### **New Features** - -- Updated. [Jamie Rees] - -- Updated appveyor. [Jamie Rees] - -- Update appveyor.yml. [Jamie] - -- Added latest version code and view. Need to finish the view #11. [tidusjar] - -- Added test button to Plex. That's fixed #9. [tidusjar] - -- Added test sonarr button #9. [tidusjar] - -- Added more tests. [tidusjar] - -- Added a bunch of logging. [tidusjar] - -- Added the application tester for CP #9. [tidusjar] - -- Added settings page for #8. [tidusjar] - -- Added pace.js. [tidusjar] - -### **Fixes** - -- Finished the notes! Resolved #7. [Jamie Rees] - -- #12. [Jamie Rees] - -- #12. [Jamie Rees] - -- Finished the status page #11 and some more work to #12. [Jamie Rees] - -- Resolved #7. [tidusjar] - -- Small changes. [tidusjar] - -- Yeah... [tidusjar] - -- Fixed #5 and also added some tests to the availability checker. [tidusjar] - -- Started added tests. [Jamie Rees] - -- Fixed an issue where the issues text appears larger. [Jamie Rees] - - -## v1.1 (2016-03-13) - -### **New Features** - -- Update appveyor.yml. [Jamie] - -- Updated readme. [Jamie Rees] - -- Added the support for TV Series integrating with Sonarr. [Jamie Rees] - -- Added the functionality to pass a port through an argument. [tidusjar] - -- Added the code to get the quality profiles from Sonarr Started plugging that into the UI. [Jamie Rees] - -- Added the spinners #3. [tidusjar] - -- Added the functionality for the admin to clear the issues. [tidusjar] - -- Added the issues to the requests page. [tidusjar] - -- Added user logout method and unit tests to cover it. [tidusjar] - -- Added DeniedUsers to the view. [tidusjar] - -- Added the denied user check to the UserLoginModule. added a test case to cover it. [tidusjar] - -- Added a missing reference. [tidusjar] - -- Added first real test. [tidusjar] - -- Update README.md. [Jamie] - -- Added the latest version of nuget. [tidusjar] - -- Added travisyml. [tidusjar] - -- Added logging. [tidusjar] - -- Added missing files. [tidusjar] - -- Update README.md. [Jamie] - -- Added logging (Still WIP) [tidusjar] - -- Added favicon and also structured the HTML correctly. [tidusjar] - -- Updated the packages so everything is now with the correct framework (4.5.2) [tidusjar] - -- Added in deletion of requests. [tidusjar] - -- Added test code. [tidusjar] - -- Added dashboard. [tidusjar] - -- Added couchpotato page. [Jamie Rees] - -- Added readme to the project and updated it. [Jamie Rees] - -- Added helpers. [tidusjar] - -### **Fixes** - -- Bug fix, Couchpotato settings wouldn't show in release due to a Nancy bug. [Jamie Rees] - -- Small changes. [Jamie Rees] - -- First release, build 1.0.0. [Jamie Rees] - -- Removed the request limit since it's not currently being used. [Jamie Rees] - -- REmoved Sickbeared for the first release. [Jamie Rees] - -- Fixed #4 We now can manually set the status of a request. [tidusjar] - -- Made the pass in the port a bit more robust. [tidusjar] - -- Styling, Added the functionality for the Sonarr Profiles on the Admin page #2 resolved. [tidusjar] - -- Fixed a bug in the Login and added a unit test to cover that. Added a button to approve an individual request. Fixed some minor bugs in the request screen. [Jamie Rees] - -- Fixed the 'responsive' issue for the search and requests pages #3. [tidusjar] - -- Styling! #3. [tidusjar] - -- Navbar category now will follow you to various screens #3. [tidusjar] - -- Fixed bugs with the 'other' reporting issue and also the clear issues. [tidusjar] - -- We now are appending the users name to who wrote the comment. Rather than it being unknown. [tidusjar] - -- More work on submitting issues. [tidusjar] - -- More test changes. [tidusjar] - -- More tests to cover the login. [tidusjar] - -- Refactoring. [tidusjar] - -- Implimented the password part and authentication with Plex. [tidusjar] - -- Initial Use authentication is working. Need to do the password bit. [tidusjar] - -- Some error handling and ensure we are an admin to delete requests. [tidusjar] - -- Fixed the issue where the Release build would not show the admin screens! [tidusjar] - -- Fixes. [tidusjar] - -- Removed the DI part of the service. TinyIOC doesn't want to work with FluentScheduler. [tidusjar] - -- First pass at the plex update service. [tidusjar] - -- Small changes. [Jamie Rees] - -- Started to impliment the Plex checker. This will check plex every x minutes to see if there is any new content and then update the avalibility of the requests. [Jamie Rees] - -- Mre work. [Jamie Rees] - -- Few small changes, added plex settings. [Jamie Rees] - -- Making the configuration actually do something. Setting a default configuration if there is no DB. [Jamie Rees] - -- Remove post build. [Jamie Rees] - -- Small changes. [Jamie Rees] - -- MOre work. [Jamie Rees] - -- Fixed the issue when sending movies to CouchPotato. [Jamie Rees] - -- Add appveyor. [tidusjar] - -- Build it on 4.5. [tidusjar] - -- Upgraded .net to 4.6. [tidusjar] - -- Typo2. [tidusjar] - -- Typo. [tidusjar] - -- Another update. [tidusjar] - -- Fixed. [tidusjar] - -- More logging to figure out why the we cannot access the admin module in a release build. [tidusjar] - -- Firstpass integrating with CouchPotato. [tidusjar] - -- Some styling. [tidusjar] - -- Fixed the plex friends. Added some unit tests, moved the plex auth into it's own page. [tidusjar] - -- Fully switched the TV shows over to use the other provider. [Jamie Rees] - -- Renamed folders. [tidusjar] - -- Assembly updates. [tidusjar] - -- Moved the rest of the projects. [tidusjar] - -- Moved UI. [tidusjar] - -- Mass rename. [tidusjar] - -- Quick changes. [tidusjar] - -- Started switching the TV over to the new provider (TheTVDB). Currently TV search is partially broken. It will search but we are not mapping all of the details. [tidusjar] - -- Implimented the new TV show Provider (needed for Sonarr TheTvDB) [tidusjar] - -- Started the user auth. [tidusjar] - -- Some work on the requests page. [tidusjar] - -- Made the 'requested' better and made the remove look nicer. [tidusjar] - -- Cleaned up the program a tiny bit. [tidusjar] - -- Removed additional namespace. [tidusjar] - -- Fixed some db issues and added a preview. [Jamie Rees] - -- More work on the settings. [Jamie Rees] - -- Upgraded Json.Net and Nancy packages. [Jamie Rees] - -- Plex friends api. [Jamie Rees] - -- Enabled trace logs. [tidusjar] - -- Sql syntax issue fixed. [tidusjar] - -- Fixed release build. [tidusjar] - -- Small updates including assembly version. [tidusjar] - -- Work on the requests page mostly done. [tidusjar] - -- Work on the TV request. the `latest` parameter is not being passed into the requestTvshow. [tidusjar] - -- Missing file. [tidusjar] - -- Using the IoC container now. [tidusjar] - -- Some plex work. [Jamie Rees] - -- More work. [Jamie Rees] - -- Removed the setup code out of the startup, since we attemtp to connect to the DB before that. [Jamie Rees] - -- Some more work. Need to stop the form submitting on a request. [tidusjar] - -- Moved everything up a directory. [tidusjar] - -- Lots of work! [tidusjar] - -- Done most of the movie search work. [Jamie Rees] - -- First pass with RequestPlex. [tidusjar] - -- Initial commit. [Jamie] - - From e3adbcb51e5a43e733160a9889e31f3d8af47f44 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Tue, 24 Apr 2018 14:05:18 +0100 Subject: [PATCH 060/495] Fixed the error from upgrading the hangfire package !wip --- src/Ombi/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/Startup.cs b/src/Ombi/Startup.cs index be78c5b11..fc80be838 100644 --- a/src/Ombi/Startup.cs +++ b/src/Ombi/Startup.cs @@ -134,7 +134,7 @@ namespace Ombi { x.UseSQLiteStorage(sqliteStorage); x.UseActivator(new IoCJobActivator(services.BuildServiceProvider())); - x.UseConsole(); + //x.UseConsole(); }); From 69e324c6701ce373c7562f9b6ef02f16e0f7277d Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Tue, 24 Apr 2018 14:23:07 +0100 Subject: [PATCH 061/495] Added Paging to the Movie Requests Page --- .../app/requests/movierequests.component.html | 4 +++- .../app/requests/movierequests.component.ts | 14 ++++++++------ src/Ombi/ClientApp/app/requests/requests.module.ts | 3 ++- src/Ombi/ClientApp/styles/base.scss | 4 ++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index 8eded8dba..19814b330 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -56,7 +56,7 @@
-
+
@@ -212,6 +212,8 @@
+ +
diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index e28b86eaa..bb6e17b7f 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -8,7 +8,7 @@ import { Subject } from "rxjs/Subject"; import { AuthService } from "../auth/auth.service"; import { NotificationService, RadarrService, RequestService } from "../services"; -import { FilterType, IFilter, IIssueCategory, IMovieRequests, IRadarrProfile, IRadarrRootFolder } from "../interfaces"; +import { FilterType, IFilter, IIssueCategory, IMovieRequests, IPagenator, IRadarrProfile, IRadarrRootFolder } from "../interfaces"; @Component({ selector: "movie-requests", @@ -65,8 +65,8 @@ export class MovieRequestsComponent implements OnInit { } public ngOnInit() { - this.amountToLoad = 100; - this.currentlyLoaded = 100; + this.amountToLoad = 10; + this.currentlyLoaded = 10; this.loadInit(); this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); this.filter = { @@ -74,8 +74,10 @@ export class MovieRequestsComponent implements OnInit { statusFilter: FilterType.None}; } - public loadMore() { - this.loadRequests(this.amountToLoad, this.currentlyLoaded); + public paginate(event: IPagenator) { + const skipAmount = event.first; + + this.loadRequests(this.amountToLoad, skipAmount); } public search(text: any) { @@ -226,7 +228,7 @@ export class MovieRequestsComponent implements OnInit { if(!this.movieRequests) { this.movieRequests = []; } - this.movieRequests.push.apply(this.movieRequests, x); + this.movieRequests = x; this.currentlyLoaded = currentlyLoaded + amountToLoad; }); } diff --git a/src/Ombi/ClientApp/app/requests/requests.module.ts b/src/Ombi/ClientApp/app/requests/requests.module.ts index 7bddee71c..18be5ed25 100644 --- a/src/Ombi/ClientApp/app/requests/requests.module.ts +++ b/src/Ombi/ClientApp/app/requests/requests.module.ts @@ -6,7 +6,7 @@ import { OrderModule } from "ngx-order-pipe"; import { InfiniteScrollModule } from "ngx-infinite-scroll"; -import { ButtonModule, DialogModule } from "primeng/primeng"; +import { ButtonModule, DialogModule, PaginatorModule } from "primeng/primeng"; import { MovieRequestsComponent } from "./movierequests.component"; // Request import { RequestComponent } from "./request.component"; @@ -36,6 +36,7 @@ const routes: Routes = [ SharedModule, SidebarModule, OrderModule, + PaginatorModule, ], declarations: [ RequestComponent, diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index 3a5deb2a3..2f9b35982 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -959,3 +959,7 @@ a > h4:hover { width: 94%; } +.ui-state-active { + background-color: $primary-colour-outline $i; + color: black $i; +} \ No newline at end of file From 0b2e488e8f42f90e63065a6e174d48d9d3a3773b Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Tue, 24 Apr 2018 14:44:22 +0100 Subject: [PATCH 062/495] Added paging to the TV Requests page --- .../Engine/Interfaces/IMovieRequestEngine.cs | 1 + .../Engine/Interfaces/IRequestEngine.cs | 1 + src/Ombi.Core/Engine/MovieRequestEngine.cs | 13 +++++++++ src/Ombi.Core/Engine/TvRequestEngine.cs | 17 ++++++++++-- .../app/requests/movierequests.component.html | 2 +- .../app/requests/movierequests.component.ts | 2 ++ .../app/requests/tvrequests.component.html | 1 + .../app/requests/tvrequests.component.ts | 27 +++++++++---------- .../ClientApp/app/services/request.service.ts | 8 ++++++ src/Ombi/Controllers/RequestController.cs | 18 +++++++++++++ 10 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs index bfeb4fbe0..2f765fede 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -18,5 +18,6 @@ namespace Ombi.Core.Engine.Interfaces Task ApproveMovieById(int requestId); Task DenyMovieById(int modelId); Task> Filter(FilterViewModel vm); + } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index bfb8be4e8..5dbf6e449 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -17,5 +17,6 @@ namespace Ombi.Core.Engine.Interfaces Task MarkUnavailable(int modelId); Task MarkAvailable(int modelId); + Task GetTotal(); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index f4b0ee48c..0e47a28f6 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -144,6 +144,19 @@ namespace Ombi.Core.Engine return allRequests; } + public async Task GetTotal() + { + var shouldHide = await HideFromOtherUsers(); + if (shouldHide.Hide) + { + return await MovieRepository.GetWithUser(shouldHide.UserId).CountAsync(); + } + else + { + return await MovieRepository.GetWithUser().CountAsync(); + } + } + /// /// Gets the requests. /// diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 44eec3fd1..c3f1139bf 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -130,7 +130,7 @@ namespace Ombi.Core.Engine var newRequest = tvBuilder.CreateNewRequest(tv); return await AddRequest(newRequest.NewRequest); } - + public async Task> GetRequests(int count, int position) { var shouldHide = await HideFromOtherUsers(); @@ -280,7 +280,7 @@ namespace Ombi.Core.Engine results.Background = PosterPathHelper.FixBackgroundPath(request.Background); results.QualityOverride = request.QualityOverride; results.RootFolder = request.RootFolder; - + await TvRepository.Update(results); return results; } @@ -432,6 +432,19 @@ namespace Ombi.Core.Engine }; } + public async Task GetTotal() + { + var shouldHide = await HideFromOtherUsers(); + if (shouldHide.Hide) + { + return await TvRepository.Get(shouldHide.UserId).CountAsync(); + } + else + { + return await TvRepository.Get().CountAsync(); + } + } + private async Task AddExistingRequest(ChildRequests newRequest, TvRequests existingRequest) { // Add the child diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.html b/src/Ombi/ClientApp/app/requests/movierequests.component.html index 19814b330..2431b5477 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.html +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.html @@ -213,7 +213,7 @@
- + diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index bb6e17b7f..3339b6941 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -39,6 +39,7 @@ export class MovieRequestsComponent implements OnInit { public order: string = "requestedDate"; public reverse = false; + public totalMovies: number = 100; private currentlyLoaded: number; private amountToLoad: number; @@ -269,6 +270,7 @@ export class MovieRequestsComponent implements OnInit { } private loadInit() { + this.requestService.getTotalMovies().subscribe(x => this.totalMovies = x); this.requestService.getMovieRequests(this.amountToLoad, 0) .subscribe(x => { this.movieRequests = x; diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.html b/src/Ombi/ClientApp/app/requests/tvrequests.component.html index 9a3c4d186..c15166e96 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.html @@ -120,6 +120,7 @@ + { - this.tvRequests = x; - this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad; - }); + public paginate(event: IPagenator) { + const skipAmount = event.first; + + this.requestService.getTvRequestsTree(this.amountToLoad, skipAmount) + .subscribe(x => { + this.tvRequests = x; + this.currentlyLoaded = this.currentlyLoaded + this.amountToLoad; + }); } public search(text: any) { @@ -197,6 +195,7 @@ export class TvRequestsComponent implements OnInit { } private loadInit() { + this.requestService.getTotalTv().subscribe(x => this.totalTv = x); this.requestService.getTvRequestsTree(this.amountToLoad, 0) .subscribe(x => { this.tvRequests = x; diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index a2757427d..c06b39434 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -20,6 +20,14 @@ export class RequestService extends ServiceHelpers { return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); } + public getTotalMovies(): Observable { + return this.http.get(`${this.url}Movie/total`, {headers: this.headers}); + } + + public getTotalTv(): Observable { + return this.http.get(`${this.url}tv/total`, {headers: this.headers}); + } + public requestTv(tv: ITvRequestViewModel): Observable { return this.http.post(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers}); } diff --git a/src/Ombi/Controllers/RequestController.cs b/src/Ombi/Controllers/RequestController.cs index 59bd75606..5b83a7981 100644 --- a/src/Ombi/Controllers/RequestController.cs +++ b/src/Ombi/Controllers/RequestController.cs @@ -37,6 +37,15 @@ namespace Ombi.Controllers return await MovieRequestEngine.GetRequests(count, position); } + /// + /// Gets the total amount of movie requests. + /// + [HttpGet("movie/total")] + public async Task GetTotalMovies() + { + return await MovieRequestEngine.GetTotal(); + } + /// /// Gets all movie requests. /// @@ -146,6 +155,15 @@ namespace Ombi.Controllers return await TvRequestEngine.GetRequestsTreeNode(count, position); } + /// + /// Gets the total amount of TV requests. + /// + [HttpGet("tv/total")] + public async Task GetTotalTV() + { + return await TvRequestEngine.GetTotal(); + } + /// /// Gets the tv requests. /// From d80c50a40a2333c19cfd1b9571624f24b4ab60c2 Mon Sep 17 00:00:00 2001 From: Jamie Rees Date: Tue, 24 Apr 2018 14:50:44 +0100 Subject: [PATCH 063/495] !wip changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49d85aca7..e94945927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### **New Features** +- Added paging to the TV Requests page. [Jamie Rees] + +- Added Paging to the Movie Requests Page. [Jamie Rees] + - Updated Mailkit dependancy. [Jamie Rees] - Update Hangfire, Newtonsoft and Swagger. [Jamie Rees] From 73340d4fb65a549617a9eb6c3249a41f84e059f5 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 24 Apr 2018 21:13:52 +0100 Subject: [PATCH 064/495] moved the Plex OAuth setting to the Authentication Settings !wip --- .../Settings/Models/AuthenticationSettings.cs | 1 + .../Settings/Models/External/PlexSettings.cs | 1 - src/Ombi/ClientApp/app/interfaces/ISettings.ts | 2 +- src/Ombi/ClientApp/app/login/login.component.html | 4 ++-- src/Ombi/ClientApp/app/login/login.component.ts | 1 - src/Ombi/ClientApp/app/services/settings.service.ts | 4 ---- .../authentication/authentication.component.html | 7 +++++++ .../authentication/authentication.component.ts | 1 + .../ClientApp/app/settings/plex/plex.component.html | 6 ------ src/Ombi/Controllers/SettingsController.cs | 10 ---------- 10 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/Ombi.Settings/Settings/Models/AuthenticationSettings.cs b/src/Ombi.Settings/Settings/Models/AuthenticationSettings.cs index 9ce5c72ac..f6736e7c5 100644 --- a/src/Ombi.Settings/Settings/Models/AuthenticationSettings.cs +++ b/src/Ombi.Settings/Settings/Models/AuthenticationSettings.cs @@ -12,5 +12,6 @@ namespace Ombi.Settings.Settings.Models public bool RequireLowercase { get; set; } public bool RequireNonAlphanumeric { get; set; } public bool RequireUppercase { get; set; } + public bool EnableOAuth { get; set; } // Plex OAuth } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs index a77b54a87..3faba3e42 100644 --- a/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/PlexSettings.cs @@ -6,7 +6,6 @@ namespace Ombi.Core.Settings.Models.External public sealed class PlexSettings : Ombi.Settings.Settings.Models.Settings { public bool Enable { get; set; } - public bool EnableOAuth { get; set; } public List Servers { get; set; } } diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 234e0aa5b..bf429d6d5 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -43,7 +43,6 @@ export interface IEmbyServer extends IExternalSettings { export interface IPlexSettings extends ISettings { enable: boolean; - enableOAuth: boolean; servers: IPlexServer[]; } @@ -146,6 +145,7 @@ export interface IAuthenticationSettings extends ISettings { requiredLowercase: boolean; requireNonAlphanumeric: boolean; requireUppercase: boolean; + enableOAuth: boolean; } export interface IUserManagementSettings extends ISettings { diff --git a/src/Ombi/ClientApp/app/login/login.component.html b/src/Ombi/ClientApp/app/login/login.component.html index 40a5ef5a0..d87b7121e 100644 --- a/src/Ombi/ClientApp/app/login/login.component.html +++ b/src/Ombi/ClientApp/app/login/login.component.html @@ -17,7 +17,7 @@ include the remember me checkbox

-
+
@@ -41,7 +41,7 @@ include the remember me checkbox
-
+
+
+
+ + +
+
+ - +
@@ -144,23 +143,24 @@
- +
-
-
-

{@INTRO}

+
+
+

Here is a list of Movies and TV Shows that have recently been added!

- {@RECENTLYADDED} -
+ {@RECENTLYADDED}
From 552cd5e16bf3b9e6c5f7f94ae53463e94720a8ca Mon Sep 17 00:00:00 2001 From: Anojh Date: Tue, 24 Apr 2018 18:54:17 -0700 Subject: [PATCH 066/495] Fixing some format issues --- .../Templates/NewsletterTemplate.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html index 6b2a76c9d..79d97e2fe 100644 --- a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html +++ b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html @@ -143,14 +143,14 @@ From 86e49115d06e32bc19d8078240c2cc896855e759 Mon Sep 17 00:00:00 2001 From: Anojh Date: Wed, 25 Apr 2018 01:26:34 -0700 Subject: [PATCH 067/495] CSS done for the template --- .../Templates/NewsletterTemplate.html | 195 ++++++++++-------- 1 file changed, 109 insertions(+), 86 deletions(-) diff --git a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html index 79d97e2fe..67a632ee6 100644 --- a/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html +++ b/src/Ombi.Notifications.Templates/Templates/NewsletterTemplate.html @@ -4,118 +4,141 @@ Ombi - -
+
diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.scss b/src/Ombi/ClientApp/app/search/seriesinformation.component.scss index 1a1ac35b3..c7dca5e86 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.scss +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.scss @@ -10,4 +10,16 @@ #requestFloatingBtn:hover { background-color: #555; /* Add a dark-grey background on hover */ + } + + #bannerimage { + width: 758px; + height: 140px; + background-color: black; + background-position: center; + padding-bottom:30px; + } + + .content-space { + padding-top: 10px; } \ No newline at end of file From 20100b01469a84184f380f20ef6feb08d9399b8d Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 13 Aug 2018 22:14:46 +0100 Subject: [PATCH 310/495] Fixed #2424 --- src/Ombi/webpack.config.common.ts | 5 +++-- src/Ombi/webpack.config.ts | 1 + src/Ombi/webpack.config.vendor.ts | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Ombi/webpack.config.common.ts b/src/Ombi/webpack.config.common.ts index 2d7d22ecf..54835ae2e 100644 --- a/src/Ombi/webpack.config.common.ts +++ b/src/Ombi/webpack.config.common.ts @@ -1,3 +1,4 @@ +"use strict"; import { AngularCompilerPlugin } from "@ngtools/webpack"; import * as MiniCssExtractPlugin from "mini-css-extract-plugin"; import * as path from "path"; @@ -35,7 +36,7 @@ export const WebpackCommonConfig = (env: any, type: string) => { output: { path: path.resolve(outputDir), filename: "[name].js", - chunkFilename: "[id].chunk.js", + chunkFilename: "[id].[hash].chunk.js", publicPath: "/dist/", }, module: { @@ -46,7 +47,7 @@ export const WebpackCommonConfig = (env: any, type: string) => { { test: /\.scss$/, exclude: /ClientApp/, use: [MiniCssExtractPlugin.loader, cssLoader, "sass-loader"] }, { test: /\.scss$/, include: /ClientApp(\\|\/)app/, use: ["to-string-loader", cssLoader, "sass-loader"] }, { test: /\.scss$/, include: /ClientApp(\\|\/)styles/, use: ["style-loader", cssLoader, "sass-loader"] }, - { test: /\.(png|jpg|jpeg|gif|woff|woff2|eot|ttf|svg)(\?|$)/, use: "url-loader?limit=8192" }, + { test: /\.(png|woff|woff2|eot|ttf|svg|gif)(\?|$)/, use: "url-loader?limit=100000" }, { test: /[\/\\]@angular[\/\\].+\.js$/, parser: { system: true } }, // ignore System.import warnings https://github.com/angular/angular/issues/21560 ], }, diff --git a/src/Ombi/webpack.config.ts b/src/Ombi/webpack.config.ts index 4f8b94594..0ab82c8dc 100644 --- a/src/Ombi/webpack.config.ts +++ b/src/Ombi/webpack.config.ts @@ -1,3 +1,4 @@ +"use strict"; import * as path from "path"; import { Configuration, DllReferencePlugin } from "webpack"; import * as webpackMerge from "webpack-merge"; diff --git a/src/Ombi/webpack.config.vendor.ts b/src/Ombi/webpack.config.vendor.ts index d0a34cd72..e4ad442ab 100644 --- a/src/Ombi/webpack.config.vendor.ts +++ b/src/Ombi/webpack.config.vendor.ts @@ -1,3 +1,4 @@ +"use strict"; import * as path from "path"; import * as webpack from "webpack"; import * as webpackMerge from "webpack-merge"; From b60e802ba7087b2677493551870eb9bcc6259123 Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 13 Aug 2018 22:23:04 +0100 Subject: [PATCH 311/495] Fixed #2427 --- src/Ombi.Api.Plex/PlexApi.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 4276f6203..f9de4f639 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -41,7 +41,18 @@ namespace Ombi.Api.Plex } else { - _app = settings.ApplicationName; + // Check for non-ascii characters (New .Net Core HTTPLib does not allow this) + var chars = settings.ApplicationName.ToCharArray(); + var hasNonAscii = false; + foreach (var c in chars) + { + if (c > 128) + { + hasNonAscii = true; + } + } + + _app = hasNonAscii ? "Ombi" : settings.ApplicationName; } return _app; From e4f90e6c178ed022ffe29611622405f73f0ce02b Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 13 Aug 2018 23:10:07 +0100 Subject: [PATCH 312/495] Fixed the 'loop' in the cacher #2429 --- src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs b/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs index be70d0029..fc46e88b7 100644 --- a/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs +++ b/src/Ombi.Schedule/Jobs/Plex/Models/ProcessedContent.cs @@ -8,7 +8,7 @@ namespace Ombi.Schedule.Jobs.Plex.Models public IEnumerable Content { get; set; } public IEnumerable Episodes { get; set; } - public bool HasProcessedContent => Content.Any(); - public bool HasProcessedEpisodes => Episodes.Any(); + public bool HasProcessedContent => Content?.Any() ?? false; + public bool HasProcessedEpisodes => Episodes?.Any() ?? false; } } \ No newline at end of file From 51f5bbc6a83909cd515a001cc8e2b150d996e1c3 Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 13 Aug 2018 23:10:11 +0100 Subject: [PATCH 313/495] Fixed the issue where we wouldn't correctly mark some shows as available when there was no provider id #2429 --- .../Jobs/Plex/PlexContentSync.cs | 54 ++++++++++++------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs index 796cc89a0..c39b80c1f 100644 --- a/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs +++ b/src/Ombi.Schedule/Jobs/Plex/PlexContentSync.cs @@ -381,6 +381,19 @@ namespace Ombi.Schedule.Jobs.Plex if (existingContent != null) { + // Let's make sure that we have some sort of ID e.g. Imdbid for this, + // Looks like it's possible to not have an Id for a show + // I suspect we cached that show just as it was added to Plex. + + if (!existingContent.HasImdb && !existingContent.HasTheMovieDb && !existingContent.HasTvDb) + { + var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, + existingContent.Key); + GetProviderIds(showMetadata, existingContent); + + await Repo.Update(existingContent); + } + // Just check the key if (existingKey != null) { @@ -478,10 +491,7 @@ namespace Ombi.Schedule.Jobs.Plex // But it does not contain the `guid` property that we need to pull out thetvdb id... var showMetadata = await PlexApi.GetMetadata(servers.PlexAuthToken, servers.FullUri, show.ratingKey); - var providerIds = - PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault() - .guid); - + var item = new PlexServerContent { AddedAt = DateTime.Now, @@ -492,20 +502,7 @@ namespace Ombi.Schedule.Jobs.Plex Url = PlexHelper.GetPlexMediaUrl(servers.MachineIdentifier, show.ratingKey), Seasons = new List() }; - if (providerIds.Type == ProviderType.ImdbId) - { - item.ImdbId = providerIds.ImdbId; - } - - if (providerIds.Type == ProviderType.TheMovieDbId) - { - item.TheMovieDbId = providerIds.TheMovieDb; - } - - if (providerIds.Type == ProviderType.TvDbId) - { - item.TvDbId = providerIds.TheTvDb; - } + GetProviderIds(showMetadata, item); // Let's just double check to make sure we do not have it now we have some id's var existingImdb = false; @@ -547,6 +544,27 @@ namespace Ombi.Schedule.Jobs.Plex } } + private static void GetProviderIds(PlexMetadata showMetadata, PlexServerContent existingContent) + { + var providerIds = + PlexHelper.GetProviderIdFromPlexGuid(showMetadata.MediaContainer.Metadata.FirstOrDefault() + .guid); + if (providerIds.Type == ProviderType.ImdbId) + { + existingContent.ImdbId = providerIds.ImdbId; + } + + if (providerIds.Type == ProviderType.TheMovieDbId) + { + existingContent.TheMovieDbId = providerIds.TheMovieDb; + } + + if (providerIds.Type == ProviderType.TvDbId) + { + existingContent.TvDbId = providerIds.TheTvDb; + } + } + /// /// Gets all the library sections. /// If the user has specified only certain libraries then we will only look for those From d8972cfa2a46cd6263398f635b031b1294689958 Mon Sep 17 00:00:00 2001 From: Chris Pritchard Date: Wed, 15 Aug 2018 08:39:39 +0100 Subject: [PATCH 314/495] Add cake.config --- cake.config | 2 ++ src/Ombi.sln | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 cake.config diff --git a/cake.config b/cake.config new file mode 100644 index 000000000..e797f1a5b --- /dev/null +++ b/cake.config @@ -0,0 +1,2 @@ +[Settings] +SkipVerification=true \ No newline at end of file diff --git a/src/Ombi.sln b/src/Ombi.sln index ab9f8550c..a626cfe84 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject ..\appveyor.yml = ..\appveyor.yml ..\build.cake = ..\build.cake + ..\cake.config = ..\cake.config ..\CHANGELOG.md = ..\CHANGELOG.md EndProjectSection EndProject @@ -92,7 +93,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Github", "Ombi.Api EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.SickRage", "Ombi.Api.SickRage\Ombi.Api.SickRage.csproj", "{94C9A366-2595-45EA-AABB-8E4A2E90EC5B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Notifications", "Ombi.Api.Notifications\Ombi.Api.Notifications.csproj", "{10D1FE9D-9124-42B7-B1E1-CEB99B832618}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Notifications", "Ombi.Api.Notifications\Ombi.Api.Notifications.csproj", "{10D1FE9D-9124-42B7-B1E1-CEB99B832618}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution From c1eb1e2738f56b9269f6bd45799ac59ea96dd1f2 Mon Sep 17 00:00:00 2001 From: Chris Pritchard Date: Wed, 15 Aug 2018 08:49:51 +0100 Subject: [PATCH 315/495] Initial attempt at getting anime seriestype working --- cake.config | 15 ++++++++++++++- src/Ombi.Api.Sonarr/Models/NewSeries.cs | 1 + src/Ombi.Core/Senders/TvSender.cs | 5 +++++ src/Ombi.sln | 1 - 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cake.config b/cake.config index e797f1a5b..8089cd408 100644 --- a/cake.config +++ b/cake.config @@ -1,2 +1,15 @@ +; This is the default configuration file for Cake. +; This file was downloaded from https://github.com/cake-build/resources + +[Nuget] +Source=https://api.nuget.org/v3/index.json +UseInProcessClient=true +LoadDependencies=false + +[Paths] +Tools=./tools +Addins=./tools/Addins +Modules=./tools/Modules + [Settings] -SkipVerification=true \ No newline at end of file +SkipVerification=false diff --git a/src/Ombi.Api.Sonarr/Models/NewSeries.cs b/src/Ombi.Api.Sonarr/Models/NewSeries.cs index 4d2c17308..ef18baddb 100644 --- a/src/Ombi.Api.Sonarr/Models/NewSeries.cs +++ b/src/Ombi.Api.Sonarr/Models/NewSeries.cs @@ -23,6 +23,7 @@ namespace Ombi.Api.Sonarr.Models public string cleanTitle { get; set; } public string imdbId { get; set; } public string titleSlug { get; set; } + public string seriesType { get; set; } public int id { get; set; } public List images { get; set; } diff --git a/src/Ombi.Core/Senders/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs index 2cccd6778..b9135467b 100644 --- a/src/Ombi.Core/Senders/TvSender.cs +++ b/src/Ombi.Core/Senders/TvSender.cs @@ -120,6 +120,7 @@ namespace Ombi.Core.Senders int qualityToUse; string rootFolderPath; + string seriesType; if (model.SeriesType == SeriesType.Anime) { @@ -128,6 +129,8 @@ namespace Ombi.Core.Senders // TODO make this overrideable via the UI rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPathAnime), s); int.TryParse(s.QualityProfileAnime, out qualityToUse); + seriesType = "anime"; + } else { @@ -136,6 +139,7 @@ namespace Ombi.Core.Senders // For some reason, if we haven't got one use the first root folder in Sonarr // TODO make this overrideable via the UI rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder ?? int.Parse(s.RootPath), s); + seriesType = "standard"; } if (model.ParentRequest.QualityOverride.HasValue) @@ -163,6 +167,7 @@ namespace Ombi.Core.Senders rootFolderPath = rootFolderPath, qualityProfileId = qualityToUse, titleSlug = model.ParentRequest.Title, + seriesType = seriesType, addOptions = new AddOptions { ignoreEpisodesWithFiles = true, // There shouldn't be any episodes with files, this is a new season diff --git a/src/Ombi.sln b/src/Ombi.sln index a626cfe84..f29eeb8a6 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -9,7 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject ..\appveyor.yml = ..\appveyor.yml ..\build.cake = ..\build.cake - ..\cake.config = ..\cake.config ..\CHANGELOG.md = ..\CHANGELOG.md EndProjectSection EndProject From b73f4e2058c7320bed0e5a693d707311f29977c8 Mon Sep 17 00:00:00 2001 From: Chris Pritchard Date: Wed, 15 Aug 2018 09:27:04 +0100 Subject: [PATCH 316/495] Delete cake.config --- cake.config | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 cake.config diff --git a/cake.config b/cake.config deleted file mode 100644 index 8089cd408..000000000 --- a/cake.config +++ /dev/null @@ -1,15 +0,0 @@ -; This is the default configuration file for Cake. -; This file was downloaded from https://github.com/cake-build/resources - -[Nuget] -Source=https://api.nuget.org/v3/index.json -UseInProcessClient=true -LoadDependencies=false - -[Paths] -Tools=./tools -Addins=./tools/Addins -Modules=./tools/Modules - -[Settings] -SkipVerification=false From 79fe8bb3318b6f174b65acef4fdcd1108a45e0c8 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 18 Aug 2018 01:15:17 +0100 Subject: [PATCH 317/495] !wip started on the Plex add user UI --- src/Ombi.Api.Plex/IPlexApi.cs | 1 + src/Ombi.Api.Plex/Models/PlexAdd.cs | 84 +++++++++++++++++ src/Ombi.Api.Plex/PlexApi.cs | 28 ++++++ src/Ombi.Api/Api.cs | 13 ++- src/Ombi.Api/IApi.cs | 1 + src/Ombi.Store/Entities/RequestType.cs | 4 +- src/Ombi/ClientApp/app/interfaces/IPlex.ts | 22 +++++ .../app/services/applications/plex.service.ts | 14 ++- .../usermanagement/addplexuser.component.ts | 25 +++++ .../usermanagement.component.html | 10 +- .../usermanagement.component.ts | 71 +++++++++++++- .../usermanagement/usermanagement.module.ts | 8 +- .../Controllers/External/PlexController.cs | 94 +++++++++++++++++++ .../External/PlexServersAddUserModel.cs | 9 ++ src/Ombi/Models/External/PlexUserViewModel.cs | 9 ++ 15 files changed, 379 insertions(+), 14 deletions(-) create mode 100644 src/Ombi.Api.Plex/Models/PlexAdd.cs create mode 100644 src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts create mode 100644 src/Ombi/Models/External/PlexServersAddUserModel.cs create mode 100644 src/Ombi/Models/External/PlexUserViewModel.cs diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 2dd1a638f..9cf188981 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -24,5 +24,6 @@ namespace Ombi.Api.Plex Task GetRecentlyAdded(string authToken, string uri, string sectionId); Task GetPin(int pinId); Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); + Task AddUser(string emailAddress, string serverId, string authToken, int[] libs); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/Models/PlexAdd.cs b/src/Ombi.Api.Plex/Models/PlexAdd.cs new file mode 100644 index 000000000..fb0a550d0 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/PlexAdd.cs @@ -0,0 +1,84 @@ +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace Ombi.Api.Plex.Models +{ + [XmlRoot(ElementName = "Section")] + public class Section + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "key")] + public string Key { get; set; } + [XmlAttribute(AttributeName = "title")] + public string Title { get; set; } + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + [XmlAttribute(AttributeName = "shared")] + public string Shared { get; set; } + } + + [XmlRoot(ElementName = "SharedServer")] + public class SharedServer + { + [XmlElement(ElementName = "Section")] + public List
Section { get; set; } + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "username")] + public string Username { get; set; } + [XmlAttribute(AttributeName = "email")] + public string Email { get; set; } + [XmlAttribute(AttributeName = "userID")] + public string UserID { get; set; } + [XmlAttribute(AttributeName = "accessToken")] + public string AccessToken { get; set; } + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + [XmlAttribute(AttributeName = "acceptedAt")] + public string AcceptedAt { get; set; } + [XmlAttribute(AttributeName = "invitedAt")] + public string InvitedAt { get; set; } + [XmlAttribute(AttributeName = "allowSync")] + public string AllowSync { get; set; } + [XmlAttribute(AttributeName = "allowCameraUpload")] + public string AllowCameraUpload { get; set; } + [XmlAttribute(AttributeName = "allowChannels")] + public string AllowChannels { get; set; } + [XmlAttribute(AttributeName = "allowTuners")] + public string AllowTuners { get; set; } + [XmlAttribute(AttributeName = "owned")] + public string Owned { get; set; } + } + + [XmlRoot(ElementName = "MediaContainer")] + public class PlexAdd + { + [XmlElement(ElementName = "SharedServer")] + public SharedServer SharedServer { get; set; } + [XmlAttribute(AttributeName = "friendlyName")] + public string FriendlyName { get; set; } + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + } + + [XmlRoot(ElementName = "Response")] + public class AddUserError + { + [XmlAttribute(AttributeName = "code")] + public string Code { get; set; } + [XmlAttribute(AttributeName = "status")] + public string Status { get; set; } + } + + public class PlexAddWrapper + { + public PlexAdd Add { get; set; } + public AddUserError Error { get; set; } + public bool HasError => Error != null; + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index f9de4f639..556d23b24 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -243,6 +243,34 @@ namespace Ombi.Api.Plex return request.FullUri; } + public async Task AddUser(string emailAddress, string serverId, string authToken, int[] libs) + { + var request = new Request(string.Empty, $"https://plex.tv/api/servers/{serverId}/shared_servers", HttpMethod.Post, ContentType.Xml); + await AddHeaders(request, authToken); + request.AddJsonBody(new + { + server_id = serverId, + shared_server = new + { + library_section_ids = libs.Length > 0 ? libs : new int[]{}, + invited_email = emailAddress + }, + sharing_settings = new { } + }); + var result = await Api.RequestContent(request); + try + { + var add = Api.DeserializeXml(result); + return new PlexAddWrapper{Add = add}; + } + catch (InvalidOperationException) + { + var error = Api.DeserializeXml(result); + return new PlexAddWrapper{Error = error}; + } + } + + /// /// Adds the required headers and also the authorization header /// diff --git a/src/Ombi.Api/Api.cs b/src/Ombi.Api/Api.cs index b0e7066a8..19dab7530 100644 --- a/src/Ombi.Api/Api.cs +++ b/src/Ombi.Api/Api.cs @@ -80,15 +80,20 @@ namespace Ombi.Api else { // XML - XmlSerializer serializer = new XmlSerializer(typeof(T)); - StringReader reader = new StringReader(receivedString); - var value = (T)serializer.Deserialize(reader); - return value; + return DeserializeXml(receivedString); } } } + public T DeserializeXml(string receivedString) + { + XmlSerializer serializer = new XmlSerializer(typeof(T)); + StringReader reader = new StringReader(receivedString); + var value = (T) serializer.Deserialize(reader); + return value; + } + public async Task RequestContent(Request request) { using (var httpRequestMessage = new HttpRequestMessage(request.HttpMethod, request.FullUri)) diff --git a/src/Ombi.Api/IApi.cs b/src/Ombi.Api/IApi.cs index 2b7f71bb8..e573d2d07 100644 --- a/src/Ombi.Api/IApi.cs +++ b/src/Ombi.Api/IApi.cs @@ -7,5 +7,6 @@ namespace Ombi.Api Task Request(Request request); Task Request(Request request); Task RequestContent(Request request); + T DeserializeXml(string receivedString); } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/RequestType.cs b/src/Ombi.Store/Entities/RequestType.cs index 42356985f..06cd6c069 100644 --- a/src/Ombi.Store/Entities/RequestType.cs +++ b/src/Ombi.Store/Entities/RequestType.cs @@ -6,7 +6,7 @@ namespace Ombi.Store.Entities { public enum RequestType { - TvShow, - Movie + TvShow = 0, + Movie = 1 } } diff --git a/src/Ombi/ClientApp/app/interfaces/IPlex.ts b/src/Ombi/ClientApp/app/interfaces/IPlex.ts index ccc4e0300..6e9a6b35a 100644 --- a/src/Ombi/ClientApp/app/interfaces/IPlex.ts +++ b/src/Ombi/ClientApp/app/interfaces/IPlex.ts @@ -49,6 +49,28 @@ export interface IPlexServerViewModel { servers: IPlexServerResult; } +export interface IPlexServerAddViewModel { + success: boolean; + servers: IPlexServersAdd[]; +} + +export interface IPlexServersAdd { + serverId: number; + machineId: string; + serverName: string; +} + +export interface IPlexUserViewModel { + username: string; + machineIdentifier: string; + libsSelected: number[]; +} + +export interface IPlexUserAddResponse { + success: boolean; + error: string; +} + export interface IPlexServerResult { friendlyName: string; machineIdentifier: string; diff --git a/src/Ombi/ClientApp/app/services/applications/plex.service.ts b/src/Ombi/ClientApp/app/services/applications/plex.service.ts index 5248e89f3..4a616ba33 100644 --- a/src/Ombi/ClientApp/app/services/applications/plex.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plex.service.ts @@ -6,7 +6,7 @@ import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; -import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerViewModel, IUsersModel } from "../../interfaces"; +import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerAddViewModel, IPlexServerViewModel, IPlexUserAddResponse, IPlexUserViewModel, IUsersModel } from "../../interfaces"; @Injectable() export class PlexService extends ServiceHelpers { @@ -22,10 +22,22 @@ export class PlexService extends ServiceHelpers { return this.http.post(`${this.url}servers`, JSON.stringify({ login, password }), {headers: this.headers}); } + public getServersFromSettings(): Observable { + return this.http.get(`${this.url}servers`, {headers: this.headers}); + } + public getLibraries(plexSettings: IPlexServer): Observable { return this.http.post(`${this.url}Libraries`, JSON.stringify(plexSettings), {headers: this.headers}); } + public getLibrariesFromSettings(machineId: string): Observable { + return this.http.get(`${this.url}Libraries/${machineId}`, {headers: this.headers}); + } + + public addUserToServer(user: IPlexUserViewModel): Observable { + return this.http.post(`${this.url}user`,JSON.stringify(user), {headers: this.headers}); + } + public getFriends(): Observable { return this.http.get(`${this.url}Friends`, {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts new file mode 100644 index 000000000..da63b67dd --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts @@ -0,0 +1,25 @@ +import { Component, Input } from "@angular/core"; +import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; + +@Component({ + selector: "ngbd-modal-content", +template: ` + + + +`, +}) +export class AddPlexUserComponent { + + @Input() public name: string; + + constructor(public activeModal: NgbActiveModal) { + console.log("called"); + } + +} diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index 37ea2eea5..c38e87a51 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -1,8 +1,11 @@ 

User Management

- - +
+ +
+
+
- +
-
-
-

Here is a list of Movies and TV Shows that have recently been added!

+
+
+

{@INTRO}

@@ -127,4 +130,5 @@ - \ No newline at end of file + + diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts index dc137909e..6f48d4080 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts @@ -1,10 +1,28 @@ import { Component, OnInit } from "@angular/core"; -import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; -import { IdentityService, NotificationService, SettingsService } from "../services"; +import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; +import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IPlexLibraries, IPlexServersAdd, IUser } from "../interfaces"; +import { IdentityService, NotificationService, PlexService, SettingsService } from "../services"; +import { AddPlexUserComponent } from "./addplexuser.component"; @Component({ templateUrl: "./usermanagement.component.html", + styles:[`.modal-backdrop.fade{opacity:0.5} + .fade { + opacity:1 !important; + } + .modal { + display: none; + overflow: hidden; + position: fixed; + top: 100px; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + -webkit-overflow-scrolling: touch; + outline: 0; + }`], }) export class UserManagementComponent implements OnInit { @@ -20,10 +38,20 @@ export class UserManagementComponent implements OnInit { public availableClaims: ICheckbox[]; public bulkMovieLimit?: number; public bulkEpisodeLimit?: number; + public plexEnabled: boolean; + public plexServers: IPlexServersAdd[]; + public plexLibs: IPlexLibraries; + + public plexUsername: string; + public libsSelected: number[]; + public machineId: string; constructor(private identityService: IdentityService, private settingsService: SettingsService, - private notificationService: NotificationService) { } + private notificationService: NotificationService, + private plexSettings: SettingsService, + private plexService: PlexService, + private modalService: NgbModal) { } public ngOnInit() { this.users = []; @@ -31,11 +59,18 @@ export class UserManagementComponent implements OnInit { this.users = x; }); + this.plexSettings.getPlex().subscribe(x => this.plexEnabled = x.enable); + this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.settingsService.getEmailNotificationSettings().subscribe(x => this.emailSettings = x); } + public open() { + const modalRef = this.modalService.open(AddPlexUserComponent, {container:"ombi"}); + modalRef.componentInstance.name = "World"; + } + public welcomeEmail(user: IUser) { if (!user.emailAddress) { this.notificationService.error("The user needs an email address."); @@ -118,4 +153,34 @@ export class UserManagementComponent implements OnInit { this.order = value; } + + public getServers() { + if(!this.plexEnabled) { + return this.notificationService.error("Plex is not enabled"); + } + + this.plexService.getServersFromSettings().subscribe(x => { + if(x.success) { + this.plexServers = x.servers; + } + }); + } + + public getPlexLibs(machineId: string) { + this.plexService.getLibrariesFromSettings(machineId).subscribe(x => { + if(x.successful) { + this.plexLibs = x.data; + } + }); + } + + public addUser() { + this.plexService.addUserToServer({ username: this.plexUsername, machineIdentifier: this.machineId, libsSelected: this.libsSelected}).subscribe(x => { + if(x.success) { + this.notificationService.success("User added to Plex"); + } else { + this.notificationService.error(x.error); + } + }); + } } diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts index 40dba285d..cf25446f5 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts @@ -12,11 +12,12 @@ import { UserManagementEditComponent } from "./usermanagement-edit.component"; import { UserManagementComponent } from "./usermanagement.component"; import { PipeModule } from "../pipes/pipe.module"; -import { IdentityService } from "../services"; +import { IdentityService, PlexService } from "../services"; import { AuthGuard } from "../auth/auth.guard"; import { OrderModule } from "ngx-order-pipe"; +import { AddPlexUserComponent } from "./addplexuser.component"; const routes: Routes = [ { path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, @@ -44,6 +45,10 @@ const routes: Routes = [ UserManagementAddComponent, UserManagementEditComponent, UpdateDetailsComponent, + AddPlexUserComponent, + ], + entryComponents:[ + AddPlexUserComponent, ], exports: [ RouterModule, @@ -51,6 +56,7 @@ const routes: Routes = [ providers: [ IdentityService, ConfirmationService, + PlexService, ], }) diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 6ea37e9cc..8a627b710 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -127,6 +127,100 @@ namespace Ombi.Controllers.External } } + + [HttpGet("Libraries/{machineId}")] + [PowerUser] + public async Task GetPlexLibraries(string machineId) + { + try + { + var s = await PlexSettings.GetSettingsAsync(); + var settings = s.Servers.FirstOrDefault(x => x.MachineIdentifier == machineId); + var libs = await PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri); + + return new PlexLibrariesResponse + { + Successful = true, + Data = libs + }; + } + catch (Exception e) + { + _log.LogWarning(e, "Error thrown when attempting to obtain the plex libs"); + + var message = e.InnerException != null ? $"{e.Message} - {e.InnerException.Message}" : e.Message; + return new PlexLibrariesResponse + { + Successful = false, + Message = message + }; + } + } + + [HttpPost("user")] + [PowerUser] + public async Task AddUser([FromBody] PlexUserViewModel user) + { + var s = await PlexSettings.GetSettingsAsync(); + var server = s.Servers.FirstOrDefault(x => x.MachineIdentifier == user.MachineIdentifier); + var result = await PlexApi.AddUser(user.Username, user.MachineIdentifier, server.PlexAuthToken, + user.LibsSelected); + if (result.HasError) + { + return Json(new + { + Success = false, + Error = result.Error.Status + }); + } + else + { + return Json(new + { + Success = true + }); + } + } + + /// + /// Gets the plex servers. + /// + /// The u. + /// + [HttpGet("servers")] + [PowerUser] + public async Task GetServers() + { + try + { + var s = await PlexSettings.GetSettingsAsync(); + var servers = new List(); + foreach (var plexServer in s.Servers) + { + servers.Add(new PlexServersAddUserModel + { + ServerId = plexServer.Id, + MachineId = plexServer.MachineIdentifier, + ServerName = plexServer.Name + }); + } + + return Json(new + { + Success = true, + Servers = servers + }); + } + catch (Exception e) + { + _log.LogWarning(e, "Error thrown when attempting to obtain the GetServers for Add User VM"); + return Json(new PlexServersViewModel + { + Success = false, + }); + } + } + /// /// Gets the plex servers. /// diff --git a/src/Ombi/Models/External/PlexServersAddUserModel.cs b/src/Ombi/Models/External/PlexServersAddUserModel.cs new file mode 100644 index 000000000..8d272b37e --- /dev/null +++ b/src/Ombi/Models/External/PlexServersAddUserModel.cs @@ -0,0 +1,9 @@ +namespace Ombi.Models.External +{ + public class PlexServersAddUserModel + { + public string ServerName { get; set; } + public int ServerId { get; set; } + public string MachineId { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Models/External/PlexUserViewModel.cs b/src/Ombi/Models/External/PlexUserViewModel.cs new file mode 100644 index 000000000..7dddf6a9f --- /dev/null +++ b/src/Ombi/Models/External/PlexUserViewModel.cs @@ -0,0 +1,9 @@ +namespace Ombi.Models.External +{ + public class PlexUserViewModel + { + public string Username { get; set; } + public string MachineIdentifier { get; set; } + public int[] LibsSelected { get; set; } + } +} \ No newline at end of file From 61a2a59879ab8e2d104d11dcdb8033113b2d143f Mon Sep 17 00:00:00 2001 From: TidusJar Date: Sat, 18 Aug 2018 19:16:31 +0100 Subject: [PATCH 318/495] Fixed #2440 --- src/Ombi.Api.Radarr/RadarrApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi.Api.Radarr/RadarrApi.cs b/src/Ombi.Api.Radarr/RadarrApi.cs index 1f897b60b..fd4deb140 100644 --- a/src/Ombi.Api.Radarr/RadarrApi.cs +++ b/src/Ombi.Api.Radarr/RadarrApi.cs @@ -79,7 +79,7 @@ namespace Ombi.Api.Radarr tmdbId = tmdbId, qualityProfileId = qualityId, rootFolderPath = rootPath, - titleSlug = title, + titleSlug = title + year, monitored = true, year = year, minimumAvailability = minimumAvailability From 8aba799681b7ada7f78472daca1eb974795ce3f4 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Sat, 18 Aug 2018 21:20:52 +0100 Subject: [PATCH 319/495] !wip almost finished the add plex friend --- .../usermanagement/addplexuser.component.html | 59 ++++++++++++ .../usermanagement/addplexuser.component.ts | 93 +++++++++++++++---- .../usermanagement.component.html | 2 +- .../usermanagement.component.ts | 59 +----------- src/Ombi/ClientApp/styles/base.scss | 15 +++ 5 files changed, 154 insertions(+), 74 deletions(-) create mode 100644 src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html diff --git a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html new file mode 100644 index 000000000..d21509043 --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html @@ -0,0 +1,59 @@ + + + + \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts index da63b67dd..c45dfc0e9 100644 --- a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts @@ -1,25 +1,84 @@ -import { Component, Input } from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; +import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; +import { NotificationService, PlexService } from "../services"; + +import { IPlexLibraries, IPlexServersAdd } from "../interfaces"; + @Component({ - selector: "ngbd-modal-content", -template: ` - - - -`, + selector: "ngbd-modal-content", + templateUrl: "./addplexuser.component.html", }) -export class AddPlexUserComponent { - - @Input() public name: string; +export class AddPlexUserComponent implements OnInit { + + @Input() public name: string; + + public plexServers: IPlexServersAdd[]; + public plexLibs: IPlexLibraries; + + public libsSelected: number[] = []; + + public form: FormGroup; + + constructor(public activeModal: NgbActiveModal, + private plexService: PlexService, + private notificationService: NotificationService, + private fb: FormBuilder) { + } + + public ngOnInit(): void { + this.form = this.fb.group({ + selectedServer: [null, Validators.required], + allLibsSelected: [true], + username:[null, Validators.required], + }); + this.getServers(); + } - constructor(public activeModal: NgbActiveModal) { - console.log("called"); + public getServers() { + this.plexService.getServersFromSettings().subscribe(x => { + if (x.success) { + this.plexServers = x.servers; + } + }); } + public getPlexLibs(machineId: string) { + this.plexService.getLibrariesFromSettings(machineId).subscribe(x => { + if (x.successful) { + this.plexLibs = x.data; + } + }); + } + + public selected() { + this.getPlexLibs(this.form.value.selectedServer); + } + + public checkedLib(checked: boolean, value: number) { + if(checked) { + this.libsSelected.push(value); + } else { + this.libsSelected = this.libsSelected.filter(v => v !== value); + } + } + + public onSubmit(form: FormGroup) { + debugger; + if (form.invalid) { + this.notificationService.error("Please check your entered values"); + return; + } + const libs = form.value.allLibsSelected ? this.plexLibs.mediaContainer.directory.map(x => +x.key) : this.libsSelected; + + this.plexService.addUserToServer({ username: form.value.username, machineIdentifier: form.value.selectedServer, libsSelected: libs }).subscribe(x => { + if (x.success) { + this.notificationService.success("User added to Plex"); + } else { + this.notificationService.error(x.error); + } + }); + + } } diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index c38e87a51..6d371847d 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -2,7 +2,7 @@
- +
diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts index 6f48d4080..082a6086c 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.ts @@ -1,28 +1,12 @@ import { Component, OnInit } from "@angular/core"; import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; -import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IPlexLibraries, IPlexServersAdd, IUser } from "../interfaces"; -import { IdentityService, NotificationService, PlexService, SettingsService } from "../services"; +import { ICheckbox, ICustomizationSettings, IEmailNotificationSettings, IUser } from "../interfaces"; +import { IdentityService, NotificationService, SettingsService } from "../services"; import { AddPlexUserComponent } from "./addplexuser.component"; @Component({ templateUrl: "./usermanagement.component.html", - styles:[`.modal-backdrop.fade{opacity:0.5} - .fade { - opacity:1 !important; - } - .modal { - display: none; - overflow: hidden; - position: fixed; - top: 100px; - right: 0; - bottom: 0; - left: 0; - z-index: 1050; - -webkit-overflow-scrolling: touch; - outline: 0; - }`], }) export class UserManagementComponent implements OnInit { @@ -39,18 +23,11 @@ export class UserManagementComponent implements OnInit { public bulkMovieLimit?: number; public bulkEpisodeLimit?: number; public plexEnabled: boolean; - public plexServers: IPlexServersAdd[]; - public plexLibs: IPlexLibraries; - - public plexUsername: string; - public libsSelected: number[]; - public machineId: string; constructor(private identityService: IdentityService, private settingsService: SettingsService, private notificationService: NotificationService, private plexSettings: SettingsService, - private plexService: PlexService, private modalService: NgbModal) { } public ngOnInit() { @@ -67,7 +44,7 @@ export class UserManagementComponent implements OnInit { } public open() { - const modalRef = this.modalService.open(AddPlexUserComponent, {container:"ombi"}); + const modalRef = this.modalService.open(AddPlexUserComponent, {container:"ombi", backdropClass:"custom-modal-backdrop", windowClass:"window"}); modalRef.componentInstance.name = "World"; } @@ -153,34 +130,4 @@ export class UserManagementComponent implements OnInit { this.order = value; } - - public getServers() { - if(!this.plexEnabled) { - return this.notificationService.error("Plex is not enabled"); - } - - this.plexService.getServersFromSettings().subscribe(x => { - if(x.success) { - this.plexServers = x.servers; - } - }); - } - - public getPlexLibs(machineId: string) { - this.plexService.getLibrariesFromSettings(machineId).subscribe(x => { - if(x.successful) { - this.plexLibs = x.data; - } - }); - } - - public addUser() { - this.plexService.addUserToServer({ username: this.plexUsername, machineIdentifier: this.machineId, libsSelected: this.libsSelected}).subscribe(x => { - if(x.success) { - this.notificationService.success("User added to Plex"); - } else { - this.notificationService.error(x.error); - } - }); - } } diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index d36986be8..9666e9077 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -966,3 +966,18 @@ a > h4:hover { .subject { display: inline-block; } + +.custom-modal-backdrop { + opacity: 0.5 !important; + filter: alpha(opacity=0.5); +} + +.window { + opacity: 1 !important; + top: 7%; +} + +.modal-header { + background-color: #282828; + padding-top:75px; +} \ No newline at end of file From 505929737c253c1bc5859400a9441d9442f42cfd Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 18 Aug 2018 22:18:41 +0100 Subject: [PATCH 320/495] Added the ability to invite Plex Friends from the user management screen. --- src/Ombi.Api.Plex/IPlexApi.cs | 1 + .../Models/PlexLibrariesForMachineId.cs | 66 +++++++++++++++++++ src/Ombi.Api.Plex/PlexApi.cs | 7 ++ src/Ombi/ClientApp/app/interfaces/IPlex.ts | 13 ++++ .../app/services/applications/plex.service.ts | 6 +- .../usermanagement/addplexuser.component.html | 19 ++++-- .../usermanagement/addplexuser.component.ts | 8 +-- .../usermanagement.component.html | 9 +-- src/Ombi/ClientApp/styles/base.scss | 16 ++++- .../Controllers/External/PlexController.cs | 10 +-- .../Models/External/PlexLibrariesResponse.cs | 8 +++ 11 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 9cf188981..82db74278 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -11,6 +11,7 @@ namespace Ombi.Api.Plex public interface IPlexApi { Task GetStatus(string authToken, string uri); + Task GetLibrariesForMachineId(string authToken, string machineId); Task SignIn(UserRequest user); Task GetServer(string authToken); Task GetLibrarySections(string authToken, string plexFullHost); diff --git a/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs b/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs new file mode 100644 index 000000000..17ac59b81 --- /dev/null +++ b/src/Ombi.Api.Plex/Models/PlexLibrariesForMachineId.cs @@ -0,0 +1,66 @@ +namespace Ombi.Api.Plex.Models +{ + + using System; + using System.Xml.Serialization; + using System.Collections.Generic; + + [XmlRoot(ElementName = "Section")] + public class SectionLite + { + [XmlAttribute(AttributeName = "id")] + public string Id { get; set; } + [XmlAttribute(AttributeName = "key")] + public string Key { get; set; } + [XmlAttribute(AttributeName = "type")] + public string Type { get; set; } + [XmlAttribute(AttributeName = "title")] + public string Title { get; set; } + } + + [XmlRoot(ElementName = "Server")] + public class ServerLib + { + [XmlElement(ElementName = "Section")] + public List Section { get; set; } + [XmlAttribute(AttributeName = "name")] + public string Name { get; set; } + [XmlAttribute(AttributeName = "address")] + public string Address { get; set; } + [XmlAttribute(AttributeName = "port")] + public string Port { get; set; } + [XmlAttribute(AttributeName = "version")] + public string Version { get; set; } + [XmlAttribute(AttributeName = "scheme")] + public string Scheme { get; set; } + [XmlAttribute(AttributeName = "host")] + public string Host { get; set; } + [XmlAttribute(AttributeName = "localAddresses")] + public string LocalAddresses { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "createdAt")] + public string CreatedAt { get; set; } + [XmlAttribute(AttributeName = "updatedAt")] + public string UpdatedAt { get; set; } + [XmlAttribute(AttributeName = "owned")] + public string Owned { get; set; } + [XmlAttribute(AttributeName = "synced")] + public string Synced { get; set; } + } + + [XmlRoot(ElementName = "MediaContainer")] + public class PlexLibrariesForMachineId + { + [XmlElement(ElementName = "Server")] + public ServerLib Server { get; set; } + [XmlAttribute(AttributeName = "friendlyName")] + public string FriendlyName { get; set; } + [XmlAttribute(AttributeName = "identifier")] + public string Identifier { get; set; } + [XmlAttribute(AttributeName = "machineIdentifier")] + public string MachineIdentifier { get; set; } + [XmlAttribute(AttributeName = "size")] + public string Size { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index 556d23b24..bed75b4d8 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -127,6 +127,13 @@ namespace Ombi.Api.Plex return await Api.Request(request); } + public async Task GetLibrariesForMachineId(string authToken, string machineId) + { + var request = new Request("", $"https://plex.tv/api/servers/{machineId}", HttpMethod.Get, ContentType.Xml); + await AddHeaders(request, authToken); + return await Api.Request(request); + } + /// // 192.168.1.69:32400/library/metadata/3662/allLeaves // The metadata ratingkey should be in the Cache diff --git a/src/Ombi/ClientApp/app/interfaces/IPlex.ts b/src/Ombi/ClientApp/app/interfaces/IPlex.ts index 6e9a6b35a..de3fc8cf1 100644 --- a/src/Ombi/ClientApp/app/interfaces/IPlex.ts +++ b/src/Ombi/ClientApp/app/interfaces/IPlex.ts @@ -34,6 +34,19 @@ export interface IPlexLibResponse { data: IPlexLibraries; } +export interface IPlexLibSimpleResponse { + successful: boolean; + message: string; + data: IPlexSection[]; +} + +export interface IPlexSection { + id: string; + key: string; + type: string; + title: string; +} + export interface IMediaContainer { directory: IDirectory[]; } diff --git a/src/Ombi/ClientApp/app/services/applications/plex.service.ts b/src/Ombi/ClientApp/app/services/applications/plex.service.ts index 4a616ba33..9f53d0e34 100644 --- a/src/Ombi/ClientApp/app/services/applications/plex.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/plex.service.ts @@ -6,7 +6,7 @@ import { Observable } from "rxjs"; import { ServiceHelpers } from "../service.helpers"; -import { IPlexAuthentication, IPlexLibResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerAddViewModel, IPlexServerViewModel, IPlexUserAddResponse, IPlexUserViewModel, IUsersModel } from "../../interfaces"; +import { IPlexAuthentication, IPlexLibResponse, IPlexLibSimpleResponse, IPlexOAuthViewModel, IPlexServer, IPlexServerAddViewModel, IPlexServerViewModel, IPlexUserAddResponse, IPlexUserViewModel, IUsersModel } from "../../interfaces"; @Injectable() export class PlexService extends ServiceHelpers { @@ -30,8 +30,8 @@ export class PlexService extends ServiceHelpers { return this.http.post(`${this.url}Libraries`, JSON.stringify(plexSettings), {headers: this.headers}); } - public getLibrariesFromSettings(machineId: string): Observable { - return this.http.get(`${this.url}Libraries/${machineId}`, {headers: this.headers}); + public getLibrariesFromSettings(machineId: string): Observable { + return this.http.get(`${this.url}Libraries/${machineId}`, {headers: this.headers}); } public addUserToServer(user: IPlexUserViewModel): Observable { diff --git a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html index d21509043..5f56ee710 100644 --- a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.html @@ -3,6 +3,9 @@ \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts index c45dfc0e9..1c77a0181 100644 --- a/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/addplexuser.component.ts @@ -4,7 +4,7 @@ import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap"; import { NotificationService, PlexService } from "../services"; -import { IPlexLibraries, IPlexServersAdd } from "../interfaces"; +import { IPlexSection, IPlexServersAdd } from "../interfaces"; @Component({ selector: "ngbd-modal-content", @@ -15,7 +15,7 @@ export class AddPlexUserComponent implements OnInit { @Input() public name: string; public plexServers: IPlexServersAdd[]; - public plexLibs: IPlexLibraries; + public plexLibs: IPlexSection[]; public libsSelected: number[] = []; @@ -65,12 +65,11 @@ export class AddPlexUserComponent implements OnInit { } public onSubmit(form: FormGroup) { - debugger; if (form.invalid) { this.notificationService.error("Please check your entered values"); return; } - const libs = form.value.allLibsSelected ? this.plexLibs.mediaContainer.directory.map(x => +x.key) : this.libsSelected; + const libs = form.value.allLibsSelected ? [] : this.libsSelected; this.plexService.addUserToServer({ username: form.value.username, machineIdentifier: form.value.selectedServer, libsSelected: libs }).subscribe(x => { if (x.success) { @@ -78,6 +77,7 @@ export class AddPlexUserComponent implements OnInit { } else { this.notificationService.error(x.error); } + this.activeModal.close(); }); } diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index 6d371847d..519db023f 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -1,12 +1,13 @@ 

User Management

-
- -
-
+ + +
+ +
diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index 9666e9077..7bd29aaaa 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -977,7 +977,17 @@ a > h4:hover { top: 7%; } -.modal-header { - background-color: #282828; - padding-top:75px; +.modal.fade .modal-dialog { + -webkit-transform: translate(0, 0%); + -ms-transform: translate(0, 0%); + -o-transform: translate(0, 0%); + transform: translate(0, 0%); + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; +} + +.modal-footer .btn+.btn { + margin-left: 5px; + margin-bottom: 10px; } \ No newline at end of file diff --git a/src/Ombi/Controllers/External/PlexController.cs b/src/Ombi/Controllers/External/PlexController.cs index 8a627b710..d7cefd091 100644 --- a/src/Ombi/Controllers/External/PlexController.cs +++ b/src/Ombi/Controllers/External/PlexController.cs @@ -130,18 +130,18 @@ namespace Ombi.Controllers.External [HttpGet("Libraries/{machineId}")] [PowerUser] - public async Task GetPlexLibraries(string machineId) + public async Task GetPlexLibraries(string machineId) { try { var s = await PlexSettings.GetSettingsAsync(); var settings = s.Servers.FirstOrDefault(x => x.MachineIdentifier == machineId); - var libs = await PlexApi.GetLibrarySections(settings.PlexAuthToken, settings.FullUri); + var libs = await PlexApi.GetLibrariesForMachineId(settings.PlexAuthToken, machineId); - return new PlexLibrariesResponse + return new PlexLibrariesLiteResponse { Successful = true, - Data = libs + Data = libs.Server.Section }; } catch (Exception e) @@ -149,7 +149,7 @@ namespace Ombi.Controllers.External _log.LogWarning(e, "Error thrown when attempting to obtain the plex libs"); var message = e.InnerException != null ? $"{e.Message} - {e.InnerException.Message}" : e.Message; - return new PlexLibrariesResponse + return new PlexLibrariesLiteResponse { Successful = false, Message = message diff --git a/src/Ombi/Models/External/PlexLibrariesResponse.cs b/src/Ombi/Models/External/PlexLibrariesResponse.cs index d1033f12e..e5aaa5a3c 100644 --- a/src/Ombi/Models/External/PlexLibrariesResponse.cs +++ b/src/Ombi/Models/External/PlexLibrariesResponse.cs @@ -25,6 +25,7 @@ // ************************************************************************/ #endregion +using System.Collections.Generic; using Ombi.Api.Plex.Models; namespace Ombi.Models.External @@ -35,4 +36,11 @@ namespace Ombi.Models.External public bool Successful { get; set; } public string Message { get; set; } } + + public class PlexLibrariesLiteResponse + { + public List Data { get; set; } + public bool Successful { get; set; } + public string Message { get; set; } + } } \ No newline at end of file From eecf1de303f0a9e25c718e9971cc5b99688eba9a Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 18 Aug 2018 22:22:26 +0100 Subject: [PATCH 321/495] !wip changelog --- CHANGELOG.md | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bdb3b8539..c24f499cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,134 @@ # Changelog +## (unreleased) + +### **New Features** + +- Added the ability to invite Plex Friends from the user management screen. [Jamie] + +- Added rich notifications for mobile. [Jamie] + +- Updater fixes. [Jamie] + +- Added updater test mode. [Jamie Rees] + +- Added a new API method to delete issue comments. [TidusJar] + +- Updated @ngu/carousel to beta version to remove rxjs-compat dependency. [Matt Jeanes] + +- Update to Angular 6/Webpack 4. [Matt Jeanes] + +- Update CHANGELOG.md. [Jamie] + +- Updated the way we create the wizard user, errors show now be fed back to the user. [Jamie] + +- Added Brazillian Portuguese as a language and also Polish. [Jamie] + +- Updated swagger. [Jamie] + +- Updated to 2.1.1. [Jamie] + +### **Fixes** + +- New translations en.json (Swedish) [Jamie] + +- New translations en.json (French) [Jamie] + +- Fixed #2440. [TidusJar] + +- Delete cake.config. [Chris Pritchard] + +- Initial attempt at getting anime seriestype working. [Chris Pritchard] + +- Add cake.config. [Chris Pritchard] + +- Fixed the issue where we wouldn't correctly mark some shows as available when there was no provider id #2429. [Jamie] + +- Fixed the 'loop' in the cacher #2429. [Jamie] + +- Fixed #2427. [Jamie] + +- Fixed #2424. [Jamie] + +- Fixed #2409. [Jamie] + +- More updater. [Jamie] + +- Humanize the request type enum in notifications e.g. TvShow will now appear as "Tv Show" #2416. [TidusJar] + +- Made the quality override and root folder override load when we load the show (It will now appear) [Jamie] + +- Fixed #2415 where power users could not set the Sonarr Quality Override or Root Folder Override. [Jamie] + +- #2371 Fixed the issue where certain actions would not setup the series correctly in Sonarr. [Jamie] + +- Tightened up the security from an API perspecitve. [TidusJar] + +- Stop the root folder and profile calls from erroring. [TidusJar] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Polish) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- New translations en.json (Portuguese, Brazilian) [Jamie] + +- Fixed all linting. [TidusJar] + +- Comment out envparam stuff. [Matt Jeanes] + +- Fixed prod build issue. [Matt Jeanes] + +- Missed a tiny bit. [Matt Jeanes] + +- Fix test. [Matt Jeanes] + +- Fix test build. [Matt Jeanes] + +- Linting + remove debug. [Matt Jeanes] + +- Switch to Yarn and disable auto publish in release mode. [Matt Jeanes] + +- Fix for #2409. [TidusJar] + +- 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] + +- Possible fix for #2298. [D34DC3N73R] + +- Fixed the text for #2370. [Jamie] + +- Fixed where you couldn't bulk edit the limits to 0 #2318. [Jamie] + +- Upgraded to .net 2.1.2 (Includes security fixes) [Jamie] + + ## v3.0.3477 (2018-07-18) ### **New Features** From d98dafb8d29667bd3807dd4f8336426bd27be91d Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 18 Aug 2018 22:33:42 +0100 Subject: [PATCH 322/495] #2408 Added the feature to delete comments on issues --- src/Ombi/ClientApp/app/interfaces/IIssues.ts | 1 + src/Ombi/ClientApp/app/issues/issueDetails.component.html | 8 +++++--- src/Ombi/ClientApp/app/issues/issueDetails.component.ts | 7 +++++++ src/Ombi/ClientApp/app/services/issues.service.ts | 4 ++++ src/Ombi/Controllers/IssuesController.cs | 5 +++-- src/Ombi/Models/IssueCommentChatViewModel.cs | 1 + 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/Ombi/ClientApp/app/interfaces/IIssues.ts b/src/Ombi/ClientApp/app/interfaces/IIssues.ts index 023ac131b..cd2ad53a6 100644 --- a/src/Ombi/ClientApp/app/interfaces/IIssues.ts +++ b/src/Ombi/ClientApp/app/interfaces/IIssues.ts @@ -46,6 +46,7 @@ export interface IIssueComments { } export interface IIssuesChat { + id: number; comment: string; date: Date; username: string; diff --git a/src/Ombi/ClientApp/app/issues/issueDetails.component.html b/src/Ombi/ClientApp/app/issues/issueDetails.component.html index ece2d9966..33157cd09 100644 --- a/src/Ombi/ClientApp/app/issues/issueDetails.component.html +++ b/src/Ombi/ClientApp/app/issues/issueDetails.component.html @@ -51,15 +51,17 @@
-
-

+ +
+

-
+ +

{{comment.comment}}

diff --git a/src/Ombi/ClientApp/app/issues/issueDetails.component.ts b/src/Ombi/ClientApp/app/issues/issueDetails.component.ts index 109823067..1fda9acc5 100644 --- a/src/Ombi/ClientApp/app/issues/issueDetails.component.ts +++ b/src/Ombi/ClientApp/app/issues/issueDetails.component.ts @@ -97,6 +97,13 @@ export class IssueDetailsComponent implements OnInit { }); } + public deleteComment(id: number) { + this.issueService.deleteComment(id).subscribe(x => { + this.loadComments(); + this.notificationService.success("Comment Deleted"); + }); + } + private loadComments() { this.issueService.getComments(this.issueId).subscribe(x => this.comments = x); } diff --git a/src/Ombi/ClientApp/app/services/issues.service.ts b/src/Ombi/ClientApp/app/services/issues.service.ts index aa49d58a3..41cbb4df1 100644 --- a/src/Ombi/ClientApp/app/services/issues.service.ts +++ b/src/Ombi/ClientApp/app/services/issues.service.ts @@ -56,4 +56,8 @@ export class IssuesService extends ServiceHelpers { public updateStatus(model: IUpdateStatus): Observable { return this.http.post(`${this.url}status`, JSON.stringify(model), { headers: this.headers }); } + + public deleteComment(id: number): Observable { + return this.http.delete(`${this.url}comments/${id}`, { headers: this.headers }); + } } diff --git a/src/Ombi/Controllers/IssuesController.cs b/src/Ombi/Controllers/IssuesController.cs index 07ee3216a..a228a63ef 100644 --- a/src/Ombi/Controllers/IssuesController.cs +++ b/src/Ombi/Controllers/IssuesController.cs @@ -182,6 +182,7 @@ namespace Ombi.Controllers var roles = await _userManager.GetRolesAsync(c.User); vm.Add(new IssueCommentChatViewModel { + Id = c.Id, Comment = c.Comment, Date = c.Date, Username = c.User.UserAlias, @@ -245,9 +246,9 @@ namespace Ombi.Controllers /// [HttpDelete("comments/{id:int}")] [PowerUser] - public async Task DeleteComment(int commentId) + public async Task DeleteComment(int id) { - var comment = await _issueComments.GetAll().FirstOrDefaultAsync(x => x.Id == commentId); + var comment = await _issueComments.GetAll().FirstOrDefaultAsync(x => x.Id == id); await _issueComments.Delete(comment); return true; diff --git a/src/Ombi/Models/IssueCommentChatViewModel.cs b/src/Ombi/Models/IssueCommentChatViewModel.cs index cc943263c..a75ccdfcd 100644 --- a/src/Ombi/Models/IssueCommentChatViewModel.cs +++ b/src/Ombi/Models/IssueCommentChatViewModel.cs @@ -4,6 +4,7 @@ namespace Ombi.Models { public class IssueCommentChatViewModel { + public int Id { get; set; } public string Comment { get; set; } public DateTime Date { get; set; } public string Username { get; set; } From c4b21c5f778050df08262abbd5a50d244809ce82 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 18 Aug 2018 22:38:41 +0100 Subject: [PATCH 323/495] !wip changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c24f499cf..404f1cdc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,8 @@ ### **Fixes** +- #2408 Added the feature to delete comments on issues. [Jamie] + - New translations en.json (Swedish) [Jamie] - New translations en.json (French) [Jamie] From b6645c8b58cfca7d74f44a60b9a88ed5e3686a97 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 18 Aug 2018 23:04:48 +0100 Subject: [PATCH 324/495] Made the OAuth a Popout to work with Org --- src/Ombi.Api.Plex/IPlexApi.cs | 2 +- src/Ombi.Api.Plex/PlexApi.cs | 8 ++--- .../Authentication/PlexOAuthManager.cs | 17 ++-------- .../ClientApp/app/login/login.component.ts | 31 ++++++++++++++----- src/Ombi/Controllers/TokenController.cs | 2 +- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/Ombi.Api.Plex/IPlexApi.cs b/src/Ombi.Api.Plex/IPlexApi.cs index 82db74278..343eaa2d7 100644 --- a/src/Ombi.Api.Plex/IPlexApi.cs +++ b/src/Ombi.Api.Plex/IPlexApi.cs @@ -24,7 +24,7 @@ namespace Ombi.Api.Plex Task GetAccount(string authToken); Task GetRecentlyAdded(string authToken, string uri, string sectionId); Task GetPin(int pinId); - Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard); + Task GetOAuthUrl(int pinId, string code, string applicationUrl); Task AddUser(string emailAddress, string serverId, string authToken, int[] libs); } } \ No newline at end of file diff --git a/src/Ombi.Api.Plex/PlexApi.cs b/src/Ombi.Api.Plex/PlexApi.cs index bed75b4d8..f0808622f 100644 --- a/src/Ombi.Api.Plex/PlexApi.cs +++ b/src/Ombi.Api.Plex/PlexApi.cs @@ -217,15 +217,11 @@ namespace Ombi.Api.Plex return await Api.Request(request); } - public async Task GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard) + public async Task GetOAuthUrl(int pinId, string code, string applicationUrl) { var request = new Request("auth#", "https://app.plex.tv", HttpMethod.Get); await AddHeaders(request); - var forwardUrl = wizard - ? new Request($"Wizard/OAuth/{pinId}", applicationUrl, HttpMethod.Get) - : new Request($"Login/OAuth/{pinId}", applicationUrl, HttpMethod.Get); - - request.AddQueryString("forwardUrl", forwardUrl.FullUri.ToString()); + request.AddQueryString("pinID", pinId.ToString()); request.AddQueryString("code", code); request.AddQueryString("context[device][product]", ApplicationName); diff --git a/src/Ombi.Core/Authentication/PlexOAuthManager.cs b/src/Ombi.Core/Authentication/PlexOAuthManager.cs index 803176d74..426037bb7 100644 --- a/src/Ombi.Core/Authentication/PlexOAuthManager.cs +++ b/src/Ombi.Core/Authentication/PlexOAuthManager.cs @@ -28,19 +28,6 @@ namespace Ombi.Core.Authentication return string.Empty; } - if (pin.authToken.IsNullOrEmpty()) - { - // Looks like we do not have a pin yet, we should retry a few times. - var retryCount = 0; - var retryMax = 5; - var retryWaitMs = 1000; - while (pin.authToken.IsNullOrEmpty() && retryCount < retryMax) - { - retryCount++; - await Task.Delay(retryWaitMs); - pin = await _api.GetPin(pinId); - } - } return pin.authToken; } @@ -52,14 +39,14 @@ namespace Ombi.Core.Authentication public async Task GetOAuthUrl(int pinId, string code, string websiteAddress = null) { var settings = await _customizationSettingsService.GetSettingsAsync(); - var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl, false); + var url = await _api.GetOAuthUrl(pinId, code, settings.ApplicationUrl.IsNullOrEmpty() ? websiteAddress : settings.ApplicationUrl); return url; } public async Task GetWizardOAuthUrl(int pinId, string code, string websiteAddress) { - var url = await _api.GetOAuthUrl(pinId, code, websiteAddress, true); + var url = await _api.GetOAuthUrl(pinId, code, websiteAddress); return url; } } diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 349c1600e..e4e530461 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -40,6 +40,7 @@ export class LoginComponent implements OnDestroy, OnInit { } private timer: any; + private pinTimer: any; private clientId: string; private errorBody: string; @@ -128,17 +129,33 @@ export class LoginComponent implements OnDestroy, OnInit { this.plexTv.GetPin(this.clientId, this.appName).subscribe((pin: any) => { this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => { - if (window.frameElement) { - // in frame - window.open(x.url, "_blank"); - } else { - // not in frame - window.location.href = x.url; - } + + window.open(x.url, "_blank"); + this.pinTimer = setInterval(() => { + this.getPinResult(x.pinId); + }, 10000); }); }); } + public getPinResult(pinId: number) { + this.authService.oAuth(pinId).subscribe(x => { + if(x.access_token) { + localStorage.setItem("id_token", x.access_token); + + if (this.authService.loggedIn()) { + this.router.navigate(["search"]); + return; + } + } + + }, err => { + this.notify.error(err.statusText); + + this.router.navigate(["login"]); + }); + } + public ngOnDestroy() { clearInterval(this.timer); } diff --git a/src/Ombi/Controllers/TokenController.cs b/src/Ombi/Controllers/TokenController.cs index aad367dbe..1314a741a 100644 --- a/src/Ombi/Controllers/TokenController.cs +++ b/src/Ombi/Controllers/TokenController.cs @@ -91,7 +91,7 @@ namespace Ombi.Controllers error = "Application URL has not been set" }); } - return new JsonResult(new { url = url.ToString() }); + return new JsonResult(new { url = url.ToString(), pinId = model.PlexTvPin.id }); } return new UnauthorizedResult(); From 70a3f2d02c586d1f30fb09f76f6b9e85174ca8f0 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sat, 18 Aug 2018 23:15:12 +0100 Subject: [PATCH 325/495] Fixed linting !wip --- src/Ombi/ClientApp/app/login/login.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index e4e530461..2c5a52404 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -30,6 +30,7 @@ export class LoginComponent implements OnDestroy, OnInit { public landingFlag: boolean; public baseUrl: string; public loginWithOmbi: boolean; + public pinTimer: any; public get appName(): string { if (this.customizationSettings.applicationName) { @@ -40,7 +41,6 @@ export class LoginComponent implements OnDestroy, OnInit { } private timer: any; - private pinTimer: any; private clientId: string; private errorBody: string; @@ -158,6 +158,7 @@ export class LoginComponent implements OnDestroy, OnInit { public ngOnDestroy() { clearInterval(this.timer); + clearInterval(this.pinTimer); } private cycleBackground() { From af9739d80596bbe1505bf268abf308e279dad269 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Sun, 19 Aug 2018 20:28:26 +0100 Subject: [PATCH 326/495] Made the popup a bit better !wip --- src/Ombi/ClientApp/app/login/login.component.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index 2c5a52404..abf387fa1 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -116,6 +116,7 @@ export class LoginComponent implements OnDestroy, OnInit { localStorage.setItem("id_token", x.access_token); if (this.authService.loggedIn()) { + this.ngOnDestroy(); this.router.navigate(["search"]); } else { this.notify.error(this.errorBody); @@ -130,8 +131,17 @@ export class LoginComponent implements OnDestroy, OnInit { this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => { - window.open(x.url, "_blank"); + window.open(x.url, "_blank", `toolbar=0, + location=0, + status=0, + menubar=0, + scrollbars=1, + resizable=1, + width=500, + height=500`); + this.pinTimer = setInterval(() => { + this.notify.info("Authenticating", "Loading... Please Wait"); this.getPinResult(x.pinId); }, 10000); }); @@ -144,6 +154,7 @@ export class LoginComponent implements OnDestroy, OnInit { localStorage.setItem("id_token", x.access_token); if (this.authService.loggedIn()) { + this.ngOnDestroy(); this.router.navigate(["search"]); return; } From 2a066e315e275230a9df8e5f286cbdd81bc01d6c Mon Sep 17 00:00:00 2001 From: TidusJar Date: Sun, 19 Aug 2018 20:42:23 +0100 Subject: [PATCH 327/495] Fixed #2418 --- src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs index 5ee55d167..ec5502581 100644 --- a/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs +++ b/src/Ombi.Schedule/Jobs/Sonarr/SonarrSync.cs @@ -57,6 +57,10 @@ namespace Ombi.Schedule.Jobs.Sonarr await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM SonarrEpisodeCache"); foreach (var s in sonarrSeries) { + if (!s.monitored) + { + continue; + } _log.LogDebug("Syncing series: {0}", s.title); var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri); var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile); From 77a1bf5c7821d6dac4de6a73d6ff383b42e29c2c Mon Sep 17 00:00:00 2001 From: TidusJar Date: Sun, 19 Aug 2018 21:04:24 +0100 Subject: [PATCH 328/495] Now include the release year in the issue title #2381 --- src/Ombi/ClientApp/app/search/moviesearch.component.ts | 2 +- src/Ombi/ClientApp/app/search/tvsearch.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 775a2c6c4..824308b21 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -150,7 +150,7 @@ export class MovieSearchComponent implements OnInit { public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) { this.issueRequestId = req.id; - this.issueRequestTitle = req.title; + this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; this.issueCategorySelected = catId; this.issuesBarVisible = true; this.issueProviderId = req.id.toString(); diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index 5478608bf..a41f34586 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -195,7 +195,7 @@ export class TvSearchComponent implements OnInit { public reportIssue(catId: IIssueCategory, req: ISearchTvResult) { this.issueRequestId = req.id; - this.issueRequestTitle = req.title; + this.issueRequestTitle = req.title + `(${req.firstAired})`; this.issueCategorySelected = catId; this.issuesBarVisible = true; this.issueProviderId = req.id.toString(); From e9f2d9a21d2ee2ba3e05b69fd4e8441a352b2125 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 19 Aug 2018 21:30:42 +0100 Subject: [PATCH 329/495] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 404f1cdc1..8bd996857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## (unreleased) +## v3.0.3587 (2018-08-19) ### **New Features** From e84b108c394beaa2fdf7e66254c8294cb64dcf04 Mon Sep 17 00:00:00 2001 From: Joe Groocock Date: Sun, 19 Aug 2018 22:12:35 +0100 Subject: [PATCH 330/495] Fix non-Windows builds. Fixes #2453 --- src/Ombi/gulpfile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/gulpfile.js b/src/Ombi/gulpfile.js index 1b8a62c04..5723c21cc 100644 --- a/src/Ombi/gulpfile.js +++ b/src/Ombi/gulpfile.js @@ -30,7 +30,7 @@ function getEnvOptions() { function webpack(type) { // 'webpack' instead of direct path can cause https://github.com/angular/angular-cli/issues/6417 - return run(`node node_modules\\webpack\\bin\\webpack.js --config webpack.config${type ? `.${type}` : ""}.ts${getEnvOptions()}`).exec(); + return run(`node ${path.join('node_modules', 'webpack', 'bin', 'webpack.js')} --config webpack.config${type ? `.${type}` : ""}.ts${getEnvOptions()}`).exec(); } gulp.task("vendor", () => { From ce79fec216005a18e8ea109b712ab25ab95ccbcf Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 21 Aug 2018 13:55:24 +0100 Subject: [PATCH 331/495] Stript out certain characters when sending a pushover message #2385 --- src/Ombi.Core.Tests/Ombi.Core.Tests.csproj | 8 +++--- src/Ombi.Core.Tests/StringHelperTests.cs | 26 +++++++++++++++++++ src/Ombi.Helpers/StringHelper.cs | 5 ++++ .../Agents/PushoverNotification.cs | 3 ++- 4 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 src/Ombi.Core.Tests/StringHelperTests.cs diff --git a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj index 8f0abee8f..30de4b6f0 100644 --- a/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj +++ b/src/Ombi.Core.Tests/Ombi.Core.Tests.csproj @@ -5,10 +5,10 @@ - - - - + + + + diff --git a/src/Ombi.Core.Tests/StringHelperTests.cs b/src/Ombi.Core.Tests/StringHelperTests.cs new file mode 100644 index 000000000..c1b95fcd7 --- /dev/null +++ b/src/Ombi.Core.Tests/StringHelperTests.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; + +using NUnit.Framework; +using Ombi.Helpers; + +namespace Ombi.Core.Tests +{ + [TestFixture] + public class StringHelperTests + { + [TestCaseSource(nameof(StripCharsData))] + public string StripCharacters(string str, char[] chars) + { + return str.StripCharacters(chars); + } + + private static IEnumerable StripCharsData + { + get + { + yield return new TestCaseData("this!is^a*string",new []{'!','^','*'}).Returns("thisisastring").SetName("Basic Strip Multipe Chars"); + yield return new TestCaseData("What is this madness'",new []{'\'','^','*'}).Returns("What is this madness").SetName("Basic Strip Multipe Chars"); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Helpers/StringHelper.cs b/src/Ombi.Helpers/StringHelper.cs index aba120c65..2dad81015 100644 --- a/src/Ombi.Helpers/StringHelper.cs +++ b/src/Ombi.Helpers/StringHelper.cs @@ -75,5 +75,10 @@ namespace Ombi.Helpers return -1; } + + public static string StripCharacters(this string str, params char[] chars) + { + return string.Concat(str.Where(c => !chars.Contains(c))); + } } } \ No newline at end of file diff --git a/src/Ombi.Notifications/Agents/PushoverNotification.cs b/src/Ombi.Notifications/Agents/PushoverNotification.cs index 5b82eb8a3..72af001dc 100644 --- a/src/Ombi.Notifications/Agents/PushoverNotification.cs +++ b/src/Ombi.Notifications/Agents/PushoverNotification.cs @@ -177,7 +177,8 @@ namespace Ombi.Notifications.Agents { try { - await Api.PushAsync(settings.AccessToken, model.Message, settings.UserToken); + //&+' < > + await Api.PushAsync(settings.AccessToken, model.Message.StripCharacters('&','+','<','>'), settings.UserToken); } catch (Exception e) { From eb1c2a695995b3192a9bd76596214b0aa102b426 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 22 Aug 2018 14:01:50 +0100 Subject: [PATCH 332/495] !wip added the lidarr settings ui --- src/Ombi.Api.Lidarr/ILidarrApi.cs | 14 ++ src/Ombi.Api.Lidarr/LidarrApi.cs | 2 +- src/Ombi.DependencyInjection/IocExtensions.cs | 2 + .../Ombi.DependencyInjection.csproj | 1 + src/Ombi.Helpers/CacheKeys.cs | 2 + src/Ombi/ClientApp/app/interfaces/IIssues.ts | 2 +- src/Ombi/ClientApp/app/interfaces/ILidarr.ts | 9 + src/Ombi/ClientApp/app/interfaces/IUser.ts | 2 +- src/Ombi/ClientApp/app/interfaces/index.ts | 1 + .../app/issues/issuestable.component.ts | 2 +- .../recentlyAdded/recentlyAdded.component.ts | 2 +- .../app/requests/request.component.ts | 4 +- .../app/requests/tvrequests.component.ts | 2 +- .../search/music/musicsearch.component.html | 118 ++++++++++ .../app/search/music/musicsearch.component.ts | 214 ++++++++++++++++++ .../ClientApp/app/search/search.component.ts | 4 +- .../app/services/applications/index.ts | 1 + .../services/applications/lidarr.service.ts | 29 +++ .../ClientApp/app/services/mobile.service.ts | 2 +- .../services/notificationMessage.service.ts | 2 +- .../app/services/recentlyAdded.service.ts | 2 +- .../ClientApp/app/services/search.service.ts | 4 + .../app/services/settings.service.ts | 1 + .../app/settings/jobs/jobs.component.ts | 2 +- .../notifications/newsletter.component.ts | 2 +- .../ClientApp/app/settings/settings.module.ts | 5 +- .../app/settings/settingsmenu.component.html | 9 + .../app/shared/issues-report.component.ts | 4 +- .../app/wizard/plex/plexoauth.component.ts | 2 +- .../Controllers/External/LidarrController.cs | 91 ++++++++ .../Controllers/External/RadarrController.cs | 4 +- src/Ombi/Ombi.csproj | 1 + 32 files changed, 520 insertions(+), 22 deletions(-) create mode 100644 src/Ombi.Api.Lidarr/ILidarrApi.cs create mode 100644 src/Ombi/ClientApp/app/interfaces/ILidarr.ts create mode 100644 src/Ombi/ClientApp/app/search/music/musicsearch.component.html create mode 100644 src/Ombi/ClientApp/app/search/music/musicsearch.component.ts create mode 100644 src/Ombi/ClientApp/app/services/applications/lidarr.service.ts create mode 100644 src/Ombi/Controllers/External/LidarrController.cs diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs new file mode 100644 index 000000000..6732693db --- /dev/null +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Ombi.Api.Lidarr.Models; + +namespace Ombi.Api.Lidarr +{ + public interface ILidarrApi + { + Task> AlbumLookup(string searchTerm, string apiKey, string baseUrl); + Task> ArtistLookup(string searchTerm, string apiKey, string baseUrl); + Task> GetProfiles(string apiKey, string baseUrl); + Task> GetRootFolders(string apiKey, string baseUrl); + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index 62b412fc1..2becb57aa 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -7,7 +7,7 @@ using Ombi.Api.Lidarr.Models; namespace Ombi.Api.Lidarr { - public class LidarrApi + public class LidarrApi : ILidarrApi { public LidarrApi(ILogger logger, IApi api) { diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 2644fa9c7..d0a56bbcd 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -32,6 +32,7 @@ using Ombi.Api.CouchPotato; using Ombi.Api.DogNzb; using Ombi.Api.FanartTv; using Ombi.Api.Github; +using Ombi.Api.Lidarr; using Ombi.Api.Mattermost; using Ombi.Api.Notifications; using Ombi.Api.Pushbullet; @@ -117,6 +118,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } public static void RegisterStore(this IServiceCollection services) { diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 2e7f984a7..6fe083fe3 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Ombi.Helpers/CacheKeys.cs b/src/Ombi.Helpers/CacheKeys.cs index e6c482f7b..f7a40d321 100644 --- a/src/Ombi.Helpers/CacheKeys.cs +++ b/src/Ombi.Helpers/CacheKeys.cs @@ -18,6 +18,8 @@ namespace Ombi.Helpers public const string NowPlayingMovies = nameof(NowPlayingMovies); public const string RadarrRootProfiles = nameof(RadarrRootProfiles); public const string RadarrQualityProfiles = nameof(RadarrQualityProfiles); + public const string LidarrRootFolders = nameof(LidarrRootFolders); + public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles); public const string FanartTv = nameof(FanartTv); } } diff --git a/src/Ombi/ClientApp/app/interfaces/IIssues.ts b/src/Ombi/ClientApp/app/interfaces/IIssues.ts index cd2ad53a6..dce9882ec 100644 --- a/src/Ombi/ClientApp/app/interfaces/IIssues.ts +++ b/src/Ombi/ClientApp/app/interfaces/IIssues.ts @@ -1,4 +1,4 @@ -import { IIssueCategory, IUser, RequestType } from "./"; +import { IIssueCategory, IUser, RequestType } from "."; export interface IIssues { id?: number; diff --git a/src/Ombi/ClientApp/app/interfaces/ILidarr.ts b/src/Ombi/ClientApp/app/interfaces/ILidarr.ts new file mode 100644 index 000000000..2674a7dac --- /dev/null +++ b/src/Ombi/ClientApp/app/interfaces/ILidarr.ts @@ -0,0 +1,9 @@ +export interface ILidarrRootFolder { + id: number; + path: string; +} + +export interface ILidarrProfile { + name: string; + id: number; +} diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index 0e8141b52..cd96848fb 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -1,4 +1,4 @@ -import { ICheckbox } from "./index"; +import { ICheckbox } from "."; export interface IUser { id: string; diff --git a/src/Ombi/ClientApp/app/interfaces/index.ts b/src/Ombi/ClientApp/app/interfaces/index.ts index 538e1bd95..013d4278d 100644 --- a/src/Ombi/ClientApp/app/interfaces/index.ts +++ b/src/Ombi/ClientApp/app/interfaces/index.ts @@ -14,3 +14,4 @@ export * from "./ISonarr"; export * from "./IUser"; export * from "./IIssues"; export * from "./IRecentlyAdded"; +export * from "./ILidarr"; diff --git a/src/Ombi/ClientApp/app/issues/issuestable.component.ts b/src/Ombi/ClientApp/app/issues/issuestable.component.ts index 5df8a35bd..aadfd546a 100644 --- a/src/Ombi/ClientApp/app/issues/issuestable.component.ts +++ b/src/Ombi/ClientApp/app/issues/issuestable.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { IIssues, IPagenator, IssueStatus } from "./../interfaces"; +import { IIssues, IPagenator, IssueStatus } from "../interfaces"; @Component({ selector: "issues-table", diff --git a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts index 303e530e1..d2120cdcf 100644 --- a/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts +++ b/src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts @@ -2,7 +2,7 @@ import { Component, OnInit } from "@angular/core"; import { NguCarouselConfig } from "@ngu/carousel"; import { ImageService, RecentlyAddedService } from "../services"; -import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; +import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "../interfaces"; @Component({ templateUrl: "recentlyAdded.component.html", diff --git a/src/Ombi/ClientApp/app/requests/request.component.ts b/src/Ombi/ClientApp/app/requests/request.component.ts index 08df3f2be..557bb7a07 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.ts +++ b/src/Ombi/ClientApp/app/requests/request.component.ts @@ -1,8 +1,8 @@  import { Component, OnInit } from "@angular/core"; -import { IIssueCategory } from "./../interfaces"; -import { IssuesService, SettingsService } from "./../services"; +import { IIssueCategory } from "../interfaces"; +import { IssuesService, SettingsService } from "../services"; @Component({ templateUrl: "./request.component.html", diff --git a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts index 6a17066d7..144de0206 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/tvrequests.component.ts @@ -7,7 +7,7 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { FilterType, IIssueCategory, IPagenator, IRequestsViewModel, ISonarrProfile, ISonarrRootFolder, ITvRequests, OrderType } from "../interfaces"; import { NotificationService, RequestService, SonarrService } from "../services"; -import { ImageService } from "./../services/image.service"; +import { ImageService } from "../services/image.service"; @Component({ selector: "tv-requests", diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.html b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html new file mode 100644 index 000000000..44dc345bc --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html @@ -0,0 +1,118 @@ + +
+ +
+
+ +
+
+
+
+ +
+ +
+ +
+
+
+ poster + +
+
+
+ +

{{result.title}} ({{result.releaseDate | date: 'yyyy'}})

+
+ + {{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | date: 'mediumDate'} }} + {{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | date: 'mediumDate'} }} + + + + + {{result.quality}}p + + + + + + + + + +
+
+

{{result.overview}}

+
+ + +
+
+
+ + + +
+
+
+ +
+
+
+ + + + + + +
+ + +
+ + + +
+ +
+
+
+
+ +
+
+ + + diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts new file mode 100644 index 000000000..38f02eb8b --- /dev/null +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts @@ -0,0 +1,214 @@ +import { PlatformLocation } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; +import { TranslateService } from "@ngx-translate/core"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; + +import { AuthService } from "../auth/auth.service"; +import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; +import { NotificationService, RequestService, SearchService } from "../services"; + +@Component({ + selector: "music-search", + templateUrl: "./music.component.html", +}) +export class MusicSearchComponent implements OnInit { + + public searchText: string; + public searchChanged: Subject = new Subject(); + public movieResults: ISearchMovieResult[]; + public result: IRequestEngineResult; + public searchApplied = false; + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequestTitle: string; + public issueRequestId: number; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + public defaultPoster: string; + + constructor( + private searchService: SearchService, private requestService: RequestService, + private notificationService: NotificationService, private authService: AuthService, + private readonly translate: TranslateService, private sanitizer: DomSanitizer, + private readonly platformLocation: PlatformLocation) { + + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.clearResults(); + return; + } + this.searchService.searchMusic(this.searchText) + .subscribe(x => { + this.movieResults = x; + this.searchApplied = true; + // Now let's load some extra info including IMDB Id + // This way the search is fast at displaying results. + this.getExtraInfo(); + }); + }); + this.defaultPoster = "../../../images/default_movie_poster.png"; + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base) { + this.defaultPoster = "../../.." + base + "/images/default_movie_poster.png"; + } + } + + public ngOnInit() { + this.searchText = ""; + this.movieResults = []; + this.result = { + message: "", + result: false, + errorMessage: "", + }; + this.popularMovies(); + } + + public search(text: any) { + this.searchChanged.next(text.target.value); + } + + public request(searchResult: ISearchMovieResult) { + searchResult.requested = true; + searchResult.requestProcessing = true; + searchResult.showSubscribe = false; + if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) { + searchResult.approved = true; + } + + try { + this.requestService.requestMovie({ theMovieDbId: searchResult.id }) + .subscribe(x => { + this.result = x; + + if (this.result.result) { + this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { + this.notificationService.success(x); + searchResult.processed = true; + }); + } else { + if (this.result.errorMessage && this.result.message) { + this.notificationService.warning("Request Added", `${this.result.message} - ${this.result.errorMessage}`); + } else { + this.notificationService.warning("Request Added", this.result.message ? this.result.message : this.result.errorMessage); + } + searchResult.requested = false; + searchResult.approved = false; + searchResult.processed = false; + searchResult.requestProcessing = false; + + } + }); + } catch (e) { + + searchResult.processed = false; + searchResult.requestProcessing = false; + this.notificationService.error(e); + } + } + + public popularMovies() { + this.clearResults(); + this.searchService.popularMovies() + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); + } + public nowPlayingMovies() { + this.clearResults(); + this.searchService.nowPlayingMovies() + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); + } + public topRatedMovies() { + this.clearResults(); + this.searchService.topRatedMovies() + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); + } + public upcomingMovies() { + this.clearResults(); + this.searchService.upcomingMovies() + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); + } + + public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) { + this.issueRequestId = req.id; + this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.id.toString(); + } + + public similarMovies(theMovieDbId: number) { + this.clearResults(); + this.searchService.similarMovies(theMovieDbId) + .subscribe(x => { + this.movieResults = x; + this.getExtraInfo(); + }); + } + + public subscribe(r: ISearchMovieResult) { + r.subscribed = true; + this.requestService.subscribeToMovie(r.requestId) + .subscribe(x => { + this.notificationService.success("Subscribed To Movie!"); + }); + } + + public unSubscribe(r: ISearchMovieResult) { + r.subscribed = false; + this.requestService.unSubscribeToMovie(r.requestId) + .subscribe(x => { + this.notificationService.success("Unsubscribed Movie!"); + }); + } + + private getExtraInfo() { + + this.movieResults.forEach((val, index) => { + if (val.posterPath === null) { + val.posterPath = this.defaultPoster; + } else { + val.posterPath = "https://image.tmdb.org/t/p/w300/" + val.posterPath; + } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + "https://image.tmdb.org/t/p/w1280" + val.backdropPath + ")"); + this.searchService.getMovieInformation(val.id) + .subscribe(m => { + this.updateItem(val, m); + }); + }); + } + + private updateItem(key: ISearchMovieResult, updated: ISearchMovieResult) { + const index = this.movieResults.indexOf(key, 0); + if (index > -1) { + const copy = { ...this.movieResults[index] }; + this.movieResults[index] = updated; + this.movieResults[index].background = copy.background; + this.movieResults[index].posterPath = copy.posterPath; + } + } + private clearResults() { + this.movieResults = []; + this.searchApplied = false; + } +} diff --git a/src/Ombi/ClientApp/app/search/search.component.ts b/src/Ombi/ClientApp/app/search/search.component.ts index f583266ee..4f1c6c3ad 100644 --- a/src/Ombi/ClientApp/app/search/search.component.ts +++ b/src/Ombi/ClientApp/app/search/search.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; -import { IIssueCategory } from "./../interfaces"; -import { IssuesService, SettingsService } from "./../services"; +import { IIssueCategory } from "../interfaces"; +import { IssuesService, SettingsService } from "../services"; @Component({ templateUrl: "./search.component.html", diff --git a/src/Ombi/ClientApp/app/services/applications/index.ts b/src/Ombi/ClientApp/app/services/applications/index.ts index 9fe4a5403..295a53415 100644 --- a/src/Ombi/ClientApp/app/services/applications/index.ts +++ b/src/Ombi/ClientApp/app/services/applications/index.ts @@ -6,3 +6,4 @@ export * from "./sonarr.service"; export * from "./tester.service"; export * from "./plexoauth.service"; export * from "./plextv.service"; +export * from "./lidarr.service"; diff --git a/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts new file mode 100644 index 000000000..ddd7e48ec --- /dev/null +++ b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts @@ -0,0 +1,29 @@ +import { PlatformLocation } from "@angular/common"; +import { HttpClient } from "@angular/common/http"; +import { Injectable } from "@angular/core"; +import { Observable } from "rxjs"; + +import { ILidarrProfile, ILidarrRootFolder } from "../../interfaces"; +import { ILidarrSettings } from "../../interfaces"; +import { ServiceHelpers } from "../service.helpers"; + +@Injectable() +export class LidarrService extends ServiceHelpers { + constructor(http: HttpClient, public platformLocation: PlatformLocation) { + super(http, "/api/v1/Lidarr", platformLocation); + } + + public getRootFolders(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/RootFolders/`, JSON.stringify(settings), {headers: this.headers}); + } + public getQualityProfiles(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Profiles/`, JSON.stringify(settings), {headers: this.headers}); + } + + public getRootFoldersFromSettings(): Observable { + return this.http.get(`${this.url}/RootFolders/`, {headers: this.headers}); + } + public getQualityProfilesFromSettings(): Observable { + return this.http.get(`${this.url}/Profiles/`, {headers: this.headers}); + } +} diff --git a/src/Ombi/ClientApp/app/services/mobile.service.ts b/src/Ombi/ClientApp/app/services/mobile.service.ts index 29cf5f609..1f9e3fb24 100644 --- a/src/Ombi/ClientApp/app/services/mobile.service.ts +++ b/src/Ombi/ClientApp/app/services/mobile.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { IMobileUsersViewModel } from "./../interfaces"; +import { IMobileUsersViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() diff --git a/src/Ombi/ClientApp/app/services/notificationMessage.service.ts b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts index 1e4603689..93727c5d2 100644 --- a/src/Ombi/ClientApp/app/services/notificationMessage.service.ts +++ b/src/Ombi/ClientApp/app/services/notificationMessage.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { IMassEmailModel } from "./../interfaces"; +import { IMassEmailModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; diff --git a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts index 18e24470c..c062f973b 100644 --- a/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts +++ b/src/Ombi/ClientApp/app/services/recentlyAdded.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces"; +import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() diff --git a/src/Ombi/ClientApp/app/services/search.service.ts b/src/Ombi/ClientApp/app/services/search.service.ts index 4454bda0a..e0da44613 100644 --- a/src/Ombi/ClientApp/app/services/search.service.ts +++ b/src/Ombi/ClientApp/app/services/search.service.ts @@ -68,4 +68,8 @@ export class SearchService extends ServiceHelpers { public trendingTv(): Observable { return this.http.get(`${this.url}/Tv/trending`, {headers: this.headers}); } + // Music + public searchMusic(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/` + searchTerm); + } } diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index d912d03ff..2016d10b7 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -18,6 +18,7 @@ import { IJobSettings, IJobSettingsViewModel, ILandingPageSettings, + ILidarrSettings, IMattermostNotifcationSettings, IMobileNotifcationSettings, INewsletterNotificationSettings, diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts index d0a7a8b83..c69d07731 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts @@ -3,7 +3,7 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { NotificationService, SettingsService } from "../../services"; -import { ICronTestModel } from "./../../interfaces"; +import { ICronTestModel } from "../../interfaces"; @Component({ templateUrl: "./jobs.component.html", diff --git a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts index 06ed6617a..eae7176e2 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/newsletter.component.ts @@ -2,7 +2,7 @@ import { INewsletterNotificationSettings, NotificationType } from "../../interfaces"; import { JobService, NotificationService, SettingsService } from "../../services"; -import { TesterService } from "./../../services/applications/tester.service"; +import { TesterService } from "../../services/applications/tester.service"; @Component({ templateUrl: "./newsletter.component.html", diff --git a/src/Ombi/ClientApp/app/settings/settings.module.ts b/src/Ombi/ClientApp/app/settings/settings.module.ts index 6ba7e3f52..f102a03fe 100644 --- a/src/Ombi/ClientApp/app/settings/settings.module.ts +++ b/src/Ombi/ClientApp/app/settings/settings.module.ts @@ -8,7 +8,7 @@ import { ClipboardModule } from "ngx-clipboard"; import { AuthGuard } from "../auth/auth.guard"; import { AuthService } from "../auth/auth.service"; import { - CouchPotatoService, EmbyService, IssuesService, JobService, MobileService, NotificationMessageService, PlexService, RadarrService, + CouchPotatoService, EmbyService, IssuesService, JobService, LidarrService, MobileService, NotificationMessageService, PlexService, RadarrService, SonarrService, TesterService, ValidationService, } from "../services"; @@ -22,6 +22,7 @@ import { EmbyComponent } from "./emby/emby.component"; import { IssuesComponent } from "./issues/issues.component"; import { JobsComponent } from "./jobs/jobs.component"; import { LandingPageComponent } from "./landingpage/landingpage.component"; +import { LidarrComponent } from "./lidarr/lidarr.component"; import { MassEmailComponent } from "./massemail/massemail.component"; import { DiscordComponent } from "./notifications/discord.component"; import { EmailNotificationComponent } from "./notifications/emailnotification.component"; @@ -36,7 +37,6 @@ import { TelegramComponent } from "./notifications/telegram.component"; import { OmbiComponent } from "./ombi/ombi.component"; import { PlexComponent } from "./plex/plex.component"; import { RadarrComponent } from "./radarr/radarr.component"; -import { LidarrComponent } from "./lidarr/lidarr.component"; import { SickRageComponent } from "./sickrage/sickrage.component"; import { SonarrComponent } from "./sonarr/sonarr.component"; import { UpdateComponent } from "./update/update.component"; @@ -145,6 +145,7 @@ const routes: Routes = [ EmbyService, MobileService, NotificationMessageService, + LidarrService, ], }) diff --git a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html index 6c6bb5c3f..a3e5a16e7 100644 --- a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html @@ -48,6 +48,15 @@ + +
diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts index 2d1becf3a..bf739f783 100644 --- a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts @@ -4,13 +4,13 @@ import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; import { debounceTime, distinctUntilChanged } from "rxjs/operators"; -import { AuthService } from "../auth/auth.service"; -import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; -import { NotificationService, RequestService, SearchService } from "../services"; +import { AuthService } from "../../auth/auth.service"; +import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../../interfaces"; +import { NotificationService, RequestService, SearchService } from "../../services"; @Component({ selector: "music-search", - templateUrl: "./music.component.html", + templateUrl: "./musicsearch.component.html", }) export class MusicSearchComponent implements OnInit { @@ -19,6 +19,7 @@ export class MusicSearchComponent implements OnInit { public movieResults: ISearchMovieResult[]; public result: IRequestEngineResult; public searchApplied = false; + public searchArtist: boolean; @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; @@ -44,11 +45,20 @@ export class MusicSearchComponent implements OnInit { this.clearResults(); return; } - this.searchService.searchMusic(this.searchText) + if(this.searchArtist) { + this.searchService.searchArtist(this.searchText) .subscribe(x => { this.movieResults = x; this.searchApplied = true; }); + } else { + this.searchService.searchAlbum(this.searchText) + .subscribe(x => { + this.movieResults = x; + this.searchApplied = true; + }); + } + }); this.defaultPoster = "../../../images/default_movie_poster.png"; const base = this.platformLocation.getBaseHrefFromDOM(); @@ -65,7 +75,6 @@ export class MusicSearchComponent implements OnInit { result: false, errorMessage: "", }; - this.popularMovies(); } public search(text: any) { @@ -111,77 +120,6 @@ export class MusicSearchComponent implements OnInit { } } - public popularMovies() { - this.clearResults(); - this.searchService.popularMovies() - .subscribe(x => { - this.movieResults = x; - }); - } - public nowPlayingMovies() { - this.clearResults(); - this.searchService.nowPlayingMovies() - .subscribe(x => { - this.movieResults = x; - }); - } - public topRatedMovies() { - this.clearResults(); - this.searchService.topRatedMovies() - .subscribe(x => { - this.movieResults = x; - }); - } - public upcomingMovies() { - this.clearResults(); - this.searchService.upcomingMovies() - .subscribe(x => { - this.movieResults = x; - }); - } - - public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) { - this.issueRequestId = req.id; - this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; - this.issueCategorySelected = catId; - this.issuesBarVisible = true; - this.issueProviderId = req.id.toString(); - } - - public similarMovies(theMovieDbId: number) { - this.clearResults(); - this.searchService.similarMovies(theMovieDbId) - .subscribe(x => { - this.movieResults = x; - this.getExtraInfo(); - }); - } - - public subscribe(r: ISearchMovieResult) { - r.subscribed = true; - this.requestService.subscribeToMovie(r.requestId) - .subscribe(x => { - this.notificationService.success("Subscribed To Movie!"); - }); - } - - public unSubscribe(r: ISearchMovieResult) { - r.subscribed = false; - this.requestService.unSubscribeToMovie(r.requestId) - .subscribe(x => { - this.notificationService.success("Unsubscribed Movie!"); - }); - } - - private updateItem(key: ISearchMovieResult, updated: ISearchMovieResult) { - const index = this.movieResults.indexOf(key, 0); - if (index > -1) { - const copy = { ...this.movieResults[index] }; - this.movieResults[index] = updated; - this.movieResults[index].background = copy.background; - this.movieResults[index].posterPath = copy.posterPath; - } - } private clearResults() { this.movieResults = []; this.searchApplied = false; diff --git a/src/Ombi/ClientApp/app/search/search.component.html b/src/Ombi/ClientApp/app/search/search.component.html index 398bfd311..046635812 100644 --- a/src/Ombi/ClientApp/app/search/search.component.html +++ b/src/Ombi/ClientApp/app/search/search.component.html @@ -13,6 +13,9 @@
  • {{ 'Search.TvTab' | translate }}
  • +
  • + {{ 'Search.MusicTab' | translate }} +
  • @@ -25,6 +28,9 @@
    +
    + +
    diff --git a/src/Ombi/ClientApp/app/search/search.component.ts b/src/Ombi/ClientApp/app/search/search.component.ts index 4f1c6c3ad..74221e71c 100644 --- a/src/Ombi/ClientApp/app/search/search.component.ts +++ b/src/Ombi/ClientApp/app/search/search.component.ts @@ -9,8 +9,10 @@ import { IssuesService, SettingsService } from "../services"; export class SearchComponent implements OnInit { public showTv: boolean; public showMovie: boolean; + public showMusic: boolean; public issueCategories: IIssueCategory[]; public issuesEnabled = false; + public musicEnabled: boolean; constructor(private issuesService: IssuesService, private settingsService: SettingsService) { @@ -18,8 +20,10 @@ export class SearchComponent implements OnInit { } public ngOnInit() { + this.settingsService.getLidarr().subscribe(x => this.musicEnabled = x.enabled); this.showMovie = true; this.showTv = false; + this.showMusic = false; this.issuesService.getCategories().subscribe(x => this.issueCategories = x); this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled); } @@ -27,10 +31,17 @@ export class SearchComponent implements OnInit { public selectMovieTab() { this.showMovie = true; this.showTv = false; + this.showMusic = false; } public selectTvTab() { this.showMovie = false; this.showTv = true; + this.showMusic = false; + } + public selectMusicTab() { + this.showMovie = false; + this.showTv = false; + this.showMusic = true; } } diff --git a/src/Ombi/ClientApp/app/search/search.module.ts b/src/Ombi/ClientApp/app/search/search.module.ts index 855207616..3e8181807 100644 --- a/src/Ombi/ClientApp/app/search/search.module.ts +++ b/src/Ombi/ClientApp/app/search/search.module.ts @@ -7,6 +7,7 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { MovieSearchComponent } from "./moviesearch.component"; import { MovieSearchGridComponent } from "./moviesearchgrid.component"; +import { MusicSearchComponent } from "./music/musicsearch.component"; import { SearchComponent } from "./search.component"; import { SeriesInformationComponent } from "./seriesinformation.component"; import { TvSearchComponent } from "./tvsearch.component"; @@ -41,6 +42,7 @@ const routes: Routes = [ TvSearchComponent, SeriesInformationComponent, MovieSearchGridComponent, + MusicSearchComponent, ], exports: [ RouterModule, diff --git a/src/Ombi/ClientApp/app/services/search.service.ts b/src/Ombi/ClientApp/app/services/search.service.ts index e0da44613..7d2783dd9 100644 --- a/src/Ombi/ClientApp/app/services/search.service.ts +++ b/src/Ombi/ClientApp/app/services/search.service.ts @@ -69,7 +69,10 @@ export class SearchService extends ServiceHelpers { return this.http.get(`${this.url}/Tv/trending`, {headers: this.headers}); } // Music - public searchMusic(searchTerm: string): Observable { - return this.http.get(`${this.url}/Music/` + searchTerm); + public searchArtist(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/Artist/` + searchTerm); + } + public searchAlbum(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/Album/` + searchTerm); } } diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index 7e5571f14..91d7ead80 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -3,8 +3,7 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { ILidarrSettings, IMinimumAvailability, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; import { IRadarrSettings } from "../../interfaces"; -import { RadarrService } from "../../services"; -import { TesterService } from "../../services"; +import { LidarrService, TesterService } from "../../services"; import { NotificationService } from "../../services"; import { SettingsService } from "../../services"; @@ -22,7 +21,7 @@ export class LidarrComponent implements OnInit { public form: FormGroup; constructor(private settingsService: SettingsService, - private radarrService: RadarrService, + private lidarrService: LidarrService, private notificationService: NotificationService, private fb: FormBuilder, private testerService: TesterService) { } @@ -59,7 +58,7 @@ export class LidarrComponent implements OnInit { public getProfiles(form: FormGroup) { this.profilesRunning = true; - this.radarrService.getQualityProfiles(form.value).subscribe(x => { + this.lidarrService.getQualityProfiles(form.value).subscribe(x => { this.qualities = x; this.qualities.unshift({ name: "Please Select", id: -1 }); @@ -70,7 +69,7 @@ export class LidarrComponent implements OnInit { public getRootFolders(form: FormGroup) { this.rootFoldersRunning = true; - this.radarrService.getRootFolders(form.value).subscribe(x => { + this.lidarrService.getRootFolders(form.value).subscribe(x => { this.rootFolders = x; this.rootFolders.unshift({ path: "Please Select", id: -1 }); diff --git a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html index a3e5a16e7..a8d89ab1c 100644 --- a/src/Ombi/ClientApp/app/settings/settingsmenu.component.html +++ b/src/Ombi/ClientApp/app/settings/settingsmenu.component.html @@ -50,7 +50,7 @@
    + [issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"> \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts index e17187355..073e64378 100644 --- a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts @@ -22,7 +22,7 @@ export class MusicSearchComponent implements OnInit { public albumResult: ISearchAlbumResult[]; public result: IRequestEngineResult; public searchApplied = false; - public searchAlbum: boolean; + public searchAlbum: boolean = true; @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; @@ -54,6 +54,9 @@ export class MusicSearchComponent implements OnInit { return; } if(this.searchAlbum) { + if(!this.searchText) { + this.searchText = "iowa"; // REMOVE + } this.searchService.searchAlbum(this.searchText) .subscribe(x => { this.albumResult = x; @@ -167,9 +170,15 @@ export class MusicSearchComponent implements OnInit { private setAlbumBackground() { this.albumResult.forEach((val, index) => { - if (val.cover === null) { - val.cover = this.defaultPoster; + if (val.disk === null) { + if(val.cover === null) { + val.disk = this.defaultPoster; + } else { + val.disk = val.cover; + } } + val.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + val.cover + ")"); }); } } diff --git a/src/Ombi/ClientApp/app/search/search.component.html b/src/Ombi/ClientApp/app/search/search.component.html index 046635812..35f792efa 100644 --- a/src/Ombi/ClientApp/app/search/search.component.html +++ b/src/Ombi/ClientApp/app/search/search.component.html @@ -22,11 +22,11 @@
    - +
    - +
    diff --git a/src/Ombi/ClientApp/app/search/search.component.ts b/src/Ombi/ClientApp/app/search/search.component.ts index 74221e71c..9347c87f1 100644 --- a/src/Ombi/ClientApp/app/search/search.component.ts +++ b/src/Ombi/ClientApp/app/search/search.component.ts @@ -21,9 +21,9 @@ export class SearchComponent implements OnInit { public ngOnInit() { this.settingsService.getLidarr().subscribe(x => this.musicEnabled = x.enabled); - this.showMovie = true; + this.showMovie = false; this.showTv = false; - this.showMusic = false; + this.showMusic = true; this.issuesService.getCategories().subscribe(x => this.issueCategories = x); this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled); } diff --git a/src/Ombi/ClientApp/styles/Themes/plex.scss b/src/Ombi/ClientApp/styles/Themes/plex.scss index 1475aef91..f965e72c7 100644 --- a/src/Ombi/ClientApp/styles/Themes/plex.scss +++ b/src/Ombi/ClientApp/styles/Themes/plex.scss @@ -345,11 +345,21 @@ button.list-group-item:focus { height: 100%; position: absolute; } + .album-bg { + width: 92%; + height: 100%; + position: absolute; + } .tint { width: 100%; height: 100%; position: absolute; } + .album-tint { + width: 92%; + height: 100%; + position: absolute; + } table.table > thead > tr > th.active { background-color: $primary-colour; } \ No newline at end of file From 70db3c7bca6ad348ff06be2f18eddce5f0466526 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 24 Aug 2018 16:07:53 +0100 Subject: [PATCH 341/495] Loads more work *phew* !wip --- music-placeholder.psd | Bin 0 -> 3762847 bytes src/Ombi.Api.Lidarr/ILidarrApi.cs | 3 +- src/Ombi.Api.Lidarr/LidarrApi.cs | 12 +- src/Ombi.Core/Engine/BaseMediaEngine.cs | 1 + src/Ombi.Core/Engine/IMusicRequestEngine.cs | 24 + src/Ombi.Core/Engine/MusicRequestEngine.cs | 948 +++++++++--------- src/Ombi.Core/Engine/MusicSearchEngine.cs | 6 +- src/Ombi.Core/Helpers/NotificationHelper.cs | 26 + .../Models/Requests/IRequestServiceMain.cs | 1 + .../Requests/MusicArtistRequestViewModel.cs | 12 +- src/Ombi.Core/Ombi.Core.csproj | 1 - .../Rule/Rules/Request/AutoApproveRule.cs | 2 + .../Rule/Rules/Request/CanRequestRule.cs | 16 +- .../Rule/Rules/Request/RequestLimitRule.cs | 19 +- .../Rule/Rules/Search/ExistingRule.cs | 13 +- .../Rules/Specific/SendNotificationRule.cs | 7 + src/Ombi.Core/Senders/INotificationHelper.cs | 2 + src/Ombi.DependencyInjection/IocExtensions.cs | 2 + src/Ombi.Helpers/OmbiRoles.cs | 2 + src/Ombi.Store/Context/IOmbiContext.cs | 1 + src/Ombi.Store/Context/OmbiContext.cs | 1 + src/Ombi.Store/Entities/OmbiUser.cs | 2 + src/Ombi.Store/Entities/RequestType.cs | 3 +- .../Entities/Requests/AlbumRequest.cs | 21 + .../Requests/IMusicRequestRepository.cs | 17 + .../Requests/MusicRequestRepository.cs | 72 ++ .../search/music/albumsearch.component.html | 15 +- .../app/search/music/musicsearch.component.ts | 6 +- .../app/search/search.component.html | 4 +- .../ClientApp/app/search/search.component.ts | 4 +- .../Controllers/MusicRequestController.cs | 144 +++ src/Ombi/Models/AlbumUpdateModel.cs | 33 + .../images/default-music-placeholder.png | Bin 0 -> 80771 bytes 33 files changed, 887 insertions(+), 533 deletions(-) create mode 100644 music-placeholder.psd create mode 100644 src/Ombi.Core/Engine/IMusicRequestEngine.cs create mode 100644 src/Ombi.Store/Entities/Requests/AlbumRequest.cs create mode 100644 src/Ombi.Store/Repository/Requests/IMusicRequestRepository.cs create mode 100644 src/Ombi.Store/Repository/Requests/MusicRequestRepository.cs create mode 100644 src/Ombi/Controllers/MusicRequestController.cs create mode 100644 src/Ombi/Models/AlbumUpdateModel.cs create mode 100644 src/Ombi/wwwroot/images/default-music-placeholder.png diff --git a/music-placeholder.psd b/music-placeholder.psd new file mode 100644 index 0000000000000000000000000000000000000000..5715f850afa7ce3ebfb48065c2915772b9737655 GIT binary patch literal 3762847 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 z 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

    ojAaKN}7Ph(z_TVcYu*l~-d=lSL;ZNu6-`N~FS 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

    9P)-v1g_gdghCGf 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 @CgtQu-%pRx> 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

    s9EFRzZo(FV2LPP;3+&rw=w7m`9af>u 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�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�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

    +
    -->
    diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html index 947739d6f..a4dcd6fb3 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.html @@ -22,12 +22,19 @@
    - - - The Radarr Sync is required - -
    + + + The Radarr Sync is required + + +
    + + + The Lidarr Sync is required + +
    +
    diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 5d99e391f..84192c53c 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -506,6 +506,7 @@ namespace Ombi.Controllers j.RefreshMetadata = j.RefreshMetadata.HasValue() ? j.RefreshMetadata : JobSettingsHelper.RefreshMetadata(j); j.PlexRecentlyAddedSync = j.PlexRecentlyAddedSync.HasValue() ? j.PlexRecentlyAddedSync : JobSettingsHelper.PlexRecentlyAdded(j); j.Newsletter = j.Newsletter.HasValue() ? j.Newsletter : JobSettingsHelper.Newsletter(j); + j.LidarrArtistSync = j.LidarrArtistSync.HasValue() ? j.LidarrArtistSync : JobSettingsHelper.LidarrArtistSync(j); return j; } From 3750243f1147e0826f8131efa2747b9805edacdf Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 24 Aug 2018 23:02:40 +0100 Subject: [PATCH 344/495] A lot more lidarr work, i'm done for the day wow... !wip #2313 --- src/Ombi.Api.Lidarr/Models/Statistics.cs | 2 +- src/Ombi.Core/Engine/MusicSearchEngine.cs | 13 +++++-- .../Models/Search/SearchAlbumViewModel.cs | 8 +++- .../Models/Search/SearchArtistViewModel.cs | 2 - .../Rule/Interfaces/SpecificRules.cs | 2 + .../Rule/Rules/Search/ExistingRule.cs | 23 +++++++++-- .../Rule/Rules/Search/LidarrAlbumCacheRule.cs | 36 +++++++++++++++++ .../Rules/Search/LidarrArtistCacheRule.cs | 35 +++++++++++++++++ .../Jobs/Lidarr/LidarrAlbumSync.cs | 9 +++-- src/Ombi.Store/Entities/LidarrAlbumCache.cs | 4 +- src/Ombi.Store/Entities/LidarrArtistCache.cs | 3 -- ...20180824211553_LidarrSyncJobs.Designer.cs} | 14 ++----- ...bs.cs => 20180824211553_LidarrSyncJobs.cs} | 36 +++++++---------- .../Migrations/OmbiContextModelSnapshot.cs | 12 +----- .../app/interfaces/ISearchMusicResult.ts | 9 +++-- .../search/music/albumsearch.component.html | 39 +++++++++++-------- .../app/settings/jobs/jobs.component.ts | 1 + src/Ombi/Properties/launchSettings.json | 4 +- src/Ombi/wwwroot/translations/en.json | 2 + 19 files changed, 167 insertions(+), 87 deletions(-) create mode 100644 src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs create mode 100644 src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs rename src/Ombi.Store/Migrations/{20180824202308_LidarrSyncJobs.Designer.cs => 20180824211553_LidarrSyncJobs.Designer.cs} (98%) rename src/Ombi.Store/Migrations/{20180824202308_LidarrSyncJobs.cs => 20180824211553_LidarrSyncJobs.cs} (80%) diff --git a/src/Ombi.Api.Lidarr/Models/Statistics.cs b/src/Ombi.Api.Lidarr/Models/Statistics.cs index 0f334fcdd..5d8eb4275 100644 --- a/src/Ombi.Api.Lidarr/Models/Statistics.cs +++ b/src/Ombi.Api.Lidarr/Models/Statistics.cs @@ -7,6 +7,6 @@ public int trackCount { get; set; } public int totalTrackCount { get; set; } public int sizeOnDisk { get; set; } - public decimal percentOfTracks { get; set; } + public decimal percentOfEpisodes { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs index b90fca88c..7eba0429d 100644 --- a/src/Ombi.Core/Engine/MusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -73,7 +73,7 @@ namespace Ombi.Core.Engine var vm = new List(); foreach (var r in result) { - vm.Add(MapIntoArtistVm(r)); + vm.Add(await MapIntoArtistVm(r)); } return vm; @@ -107,7 +107,7 @@ namespace Ombi.Core.Engine return await _lidarrApi.GetArtist(artistId, settings.ApiKey, settings.FullUri); } - private SearchArtistViewModel MapIntoArtistVm(ArtistLookup a) + private async Task MapIntoArtistVm(ArtistLookup a) { var vm = new SearchArtistViewModel { @@ -121,13 +121,16 @@ namespace Ombi.Core.Engine Links = a.links, Overview = a.overview, }; - + var poster = a.images?.FirstOrDefault(x => x.coverType.Equals("poaster")); if (poster == null) { vm.Poster = a.remotePoster; } + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrArtist); + return vm; } @@ -162,6 +165,10 @@ namespace Ombi.Core.Engine vm.Cover = a.remoteCover; } + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + return vm; } private LidarrSettings _settings; diff --git a/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs index b243c5374..a494a3cb5 100644 --- a/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchAlbumViewModel.cs @@ -1,8 +1,9 @@ using System; +using Ombi.Store.Entities; namespace Ombi.Core.Models.Search { - public class SearchAlbumViewModel + public class SearchAlbumViewModel : SearchViewModel { public string Title { get; set; } public string ForeignAlbumId { get; set; } @@ -14,6 +15,9 @@ namespace Ombi.Core.Models.Search public string ForeignArtistId { get; set; } public string Cover { get; set; } public string Disk { get; set; } - + public decimal PercentOfTracks { get; set; } + public override RequestType Type => RequestType.Album; + public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; + public bool FullyAvailable => PercentOfTracks == 100; } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs b/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs index da1cc892f..b736df529 100644 --- a/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs +++ b/src/Ombi.Core/Models/Search/SearchArtistViewModel.cs @@ -12,8 +12,6 @@ namespace Ombi.Core.Models.Search public string Poster { get; set; } public string Logo { get; set; } public bool Monitored { get; set; } - public bool Available { get; set; } - public bool Requested { get; set; } public string ArtistType { get; set; } public string CleanName { get; set; } public Link[] Links { get; set; } // Couldn't be bothered to map it diff --git a/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs b/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs index 522ba8a95..d432f87be 100644 --- a/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs +++ b/src/Ombi.Core/Rule/Interfaces/SpecificRules.cs @@ -3,5 +3,7 @@ public enum SpecificRules { CanSendNotification, + LidarrArtist, + LidarrAlbum, } } \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs index bd7218145..965fcdfaf 100644 --- a/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs +++ b/src/Ombi.Core/Rule/Rules/Search/ExistingRule.cs @@ -11,13 +11,15 @@ namespace Ombi.Core.Rule.Rules.Search { public class ExistingRule : BaseSearchRule, IRules { - public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv) + public ExistingRule(IMovieRequestRepository movie, ITvRequestRepository tv, IMusicRequestRepository music) { Movie = movie; Tv = tv; + Music = music; } private IMovieRequestRepository Movie { get; } + private IMusicRequestRepository Music { get; } private ITvRequestRepository Tv { get; } public async Task Execute(SearchViewModel obj) @@ -37,7 +39,7 @@ namespace Ombi.Core.Rule.Rules.Search } return Success(); } - else if (obj.Type == RequestType.Album) + if (obj.Type == RequestType.TvShow) { //var tvRequests = Tv.GetRequest(obj.Id); //if (tvRequests != null) // Do we already have a request for this? @@ -50,7 +52,7 @@ namespace Ombi.Core.Rule.Rules.Search // return Task.FromResult(Success()); //} - var request = (SearchTvShowViewModel) obj; + var request = (SearchTvShowViewModel)obj; var tvRequests = Tv.GetRequest(obj.Id); if (tvRequests != null) // Do we already have a request for this? { @@ -96,6 +98,21 @@ namespace Ombi.Core.Rule.Rules.Search return Success(); } + if (obj.Type == RequestType.Album) + { + var album = (SearchAlbumViewModel) obj; + var albumRequest = await Music.GetRequestAsync(album.ForeignAlbumId); + if (albumRequest != null) // Do we already have a request for this? + { + obj.Requested = true; + obj.RequestId = albumRequest.Id; + obj.Approved = albumRequest.Approved; + obj.Available = albumRequest.Available; + + return Success(); + } + return Success(); + } return Success(); } } diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs new file mode 100644 index 000000000..97a27d47f --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrAlbumCacheRule.cs @@ -0,0 +1,36 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Rule.Rules.Search +{ + public class LidarrAlbumCacheRule : SpecificRule, ISpecificRule + { + public LidarrAlbumCacheRule(IRepository db) + { + _db = db; + } + + private readonly IRepository _db; + + public Task Execute(object objec) + { + var obj = (SearchAlbumViewModel) objec; + // Check if it's in Lidarr + var result = _db.GetAll().FirstOrDefault(x => x.ForeignAlbumId.Equals(obj.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + if (result != null) + { + obj.PercentOfTracks = result.PercentOfTracks; + obj.Monitored = true; // It's in Lidarr so it's monitored + } + + return Task.FromResult(Success()); + } + + public override SpecificRules Rule => SpecificRules.LidarrAlbum; + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs new file mode 100644 index 000000000..db472a951 --- /dev/null +++ b/src/Ombi.Core/Rule/Rules/Search/LidarrArtistCacheRule.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Core.Models.Search; +using Ombi.Core.Rule.Interfaces; +using Ombi.Store.Entities; +using Ombi.Store.Repository; + +namespace Ombi.Core.Rule.Rules.Search +{ + public class LidarrArtistCacheRule : SpecificRule, ISpecificRule + { + public LidarrArtistCacheRule(IRepository db) + { + _db = db; + } + + private readonly IRepository _db; + + public Task Execute(object objec) + { + var obj = (SearchArtistViewModel) objec; + // Check if it's in Lidarr + var result = _db.GetAll().FirstOrDefault(x => x.ForeignArtistId.Equals(obj.ForignArtistId, StringComparison.InvariantCultureIgnoreCase)); + if (result != null) + { + obj.Monitored = true; // It's in Lidarr so it's monitored + } + + return Task.FromResult(Success()); + } + + public override SpecificRules Rule => SpecificRules.LidarrArtist; + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs index 031a9e9f5..2eae36206 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs @@ -48,23 +48,24 @@ namespace Ombi.Schedule.Jobs.Lidarr // Let's remove the old cached data await _ctx.Database.ExecuteSqlCommandAsync("DELETE FROM LidarrAlbumCache"); - var artistCache = new List(); + var albumCache = new List(); foreach (var a in albums) { if (a.id > 0) { - artistCache.Add(new LidarrAlbumCache + albumCache.Add(new LidarrAlbumCache { ArtistId = a.artistId, ForeignAlbumId = a.foreignAlbumId, ReleaseDate = a.releaseDate, TrackCount = a.currentRelease.trackCount, Monitored = a.monitored, - Title = a.title + Title = a.title, + PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m }); } } - await _ctx.LidarrAlbumCache.AddRangeAsync(artistCache); + await _ctx.LidarrAlbumCache.AddRangeAsync(albumCache); await _ctx.SaveChangesAsync(); } diff --git a/src/Ombi.Store/Entities/LidarrAlbumCache.cs b/src/Ombi.Store/Entities/LidarrAlbumCache.cs index 5fd1ffcdd..15d0a53da 100644 --- a/src/Ombi.Store/Entities/LidarrAlbumCache.cs +++ b/src/Ombi.Store/Entities/LidarrAlbumCache.cs @@ -12,8 +12,6 @@ namespace Ombi.Store.Entities public DateTime ReleaseDate { get; set; } public bool Monitored { get; set; } public string Title { get; set; } - - [ForeignKey(nameof(ArtistId))] - public LidarrArtistCache Artist { get; set; } + public decimal PercentOfTracks { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Entities/LidarrArtistCache.cs b/src/Ombi.Store/Entities/LidarrArtistCache.cs index 86daebd2b..dd78b4e2c 100644 --- a/src/Ombi.Store/Entities/LidarrArtistCache.cs +++ b/src/Ombi.Store/Entities/LidarrArtistCache.cs @@ -6,12 +6,9 @@ namespace Ombi.Store.Entities [Table("LidarrArtistCache")] public class LidarrArtistCache : Entity { - [ForeignKey(nameof(ArtistId))] public int ArtistId { get; set; } public string ArtistName { get; set; } public string ForeignArtistId { get; set; } public bool Monitored { get; set; } - - public List Albums { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Store/Migrations/20180824202308_LidarrSyncJobs.Designer.cs b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs similarity index 98% rename from src/Ombi.Store/Migrations/20180824202308_LidarrSyncJobs.Designer.cs rename to src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs index 5929fe0f5..c97886525 100644 --- a/src/Ombi.Store/Migrations/20180824202308_LidarrSyncJobs.Designer.cs +++ b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.Designer.cs @@ -9,7 +9,7 @@ using Ombi.Store.Context; namespace Ombi.Store.Migrations { [DbContext(typeof(OmbiContext))] - [Migration("20180824202308_LidarrSyncJobs")] + [Migration("20180824211553_LidarrSyncJobs")] partial class LidarrSyncJobs { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -257,6 +257,8 @@ namespace Ombi.Store.Migrations b.Property("Monitored"); + b.Property("PercentOfTracks"); + b.Property("ReleaseDate"); b.Property("Title"); @@ -265,8 +267,6 @@ namespace Ombi.Store.Migrations b.HasKey("Id"); - b.HasIndex("ArtistId"); - b.ToTable("LidarrAlbumCache"); }); @@ -965,14 +965,6 @@ namespace Ombi.Store.Migrations .HasPrincipalKey("EmbyId"); }); - modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => - { - b.HasOne("Ombi.Store.Entities.LidarrArtistCache", "Artist") - .WithMany("Albums") - .HasForeignKey("ArtistId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "User") diff --git a/src/Ombi.Store/Migrations/20180824202308_LidarrSyncJobs.cs b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs similarity index 80% rename from src/Ombi.Store/Migrations/20180824202308_LidarrSyncJobs.cs rename to src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs index e741b2294..2b843d3e2 100644 --- a/src/Ombi.Store/Migrations/20180824202308_LidarrSyncJobs.cs +++ b/src/Ombi.Store/Migrations/20180824211553_LidarrSyncJobs.cs @@ -8,49 +8,39 @@ namespace Ombi.Store.Migrations protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( - name: "LidarrArtistCache", + name: "LidarrAlbumCache", 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) + 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) }, constraints: table => { - table.PrimaryKey("PK_LidarrArtistCache", x => x.Id); + table.PrimaryKey("PK_LidarrAlbumCache", x => x.Id); }); migrationBuilder.CreateTable( - name: "LidarrAlbumCache", + name: "LidarrArtistCache", 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) + ArtistName = table.Column(nullable: true), + ForeignArtistId = table.Column(nullable: true), + Monitored = table.Column(nullable: false) }, constraints: table => { - table.PrimaryKey("PK_LidarrAlbumCache", x => x.Id); - table.ForeignKey( - name: "FK_LidarrAlbumCache_LidarrArtistCache_ArtistId", - column: x => x.ArtistId, - principalTable: "LidarrArtistCache", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); + table.PrimaryKey("PK_LidarrArtistCache", x => x.Id); }); - - migrationBuilder.CreateIndex( - name: "IX_LidarrAlbumCache_ArtistId", - table: "LidarrAlbumCache", - column: "ArtistId"); } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 893c22edc..0e3d1efea 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -255,6 +255,8 @@ namespace Ombi.Store.Migrations b.Property("Monitored"); + b.Property("PercentOfTracks"); + b.Property("ReleaseDate"); b.Property("Title"); @@ -263,8 +265,6 @@ namespace Ombi.Store.Migrations b.HasKey("Id"); - b.HasIndex("ArtistId"); - b.ToTable("LidarrAlbumCache"); }); @@ -963,14 +963,6 @@ namespace Ombi.Store.Migrations .HasPrincipalKey("EmbyId"); }); - modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => - { - b.HasOne("Ombi.Store.Entities.LidarrArtistCache", "Artist") - .WithMany("Albums") - .HasForeignKey("ArtistId") - .OnDelete(DeleteBehavior.Cascade); - }); - modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => { b.HasOne("Ombi.Store.Entities.OmbiUser", "User") diff --git a/src/Ombi/ClientApp/app/interfaces/ISearchMusicResult.ts b/src/Ombi/ClientApp/app/interfaces/ISearchMusicResult.ts index 6afd10b19..806beb92f 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISearchMusicResult.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISearchMusicResult.ts @@ -29,6 +29,8 @@ export interface ILink { } export interface ISearchAlbumResult { + id: number; + requestId: number; albumType: string; artistName: string; cover: string; @@ -39,10 +41,11 @@ export interface ISearchAlbumResult { rating: number; releaseDate: Date; title: string; - approved: boolean; + fullyAvailable: boolean; + partiallyAvailable: boolean; requested: boolean; - requestId: number; - available: boolean; + approved: boolean; + subscribed: boolean; // for the UI showSubscribe: boolean; diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html index cf98c84cc..6b5cc52a9 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -33,27 +33,32 @@ --> - - - Release Date: {{result.releaseDate | date:'yyyy-MM-dd'}} + + + + + + + + + + + - - {{result.rating}}/10 + + + + - - - - - - - - - - - - + + + Release Date: {{result.releaseDate | date:'yyyy-MM-dd'}} + + + {{result.rating}}/10 + diff --git a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts index c69d07731..756d6ba89 100644 --- a/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts +++ b/src/Ombi/ClientApp/app/settings/jobs/jobs.component.ts @@ -34,6 +34,7 @@ export class JobsComponent implements OnInit { refreshMetadata: [x.refreshMetadata, Validators.required], newsletter: [x.newsletter, Validators.required], plexRecentlyAddedSync: [x.plexRecentlyAddedSync, Validators.required], + lidarrArtistSync: [x.lidarrArtistSync, Validators.required], }); }); } diff --git a/src/Ombi/Properties/launchSettings.json b/src/Ombi/Properties/launchSettings.json index 96b1ceb6e..19e5d23af 100644 --- a/src/Ombi/Properties/launchSettings.json +++ b/src/Ombi/Properties/launchSettings.json @@ -3,14 +3,14 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:3579/", + "applicationUrl": "http://localhost:3577/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", - "commandLineArgs": "--host http://*:3579", + "commandLineArgs": "--host http://*:3577", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 1d195d251..4fd3ccb1b 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -12,6 +12,8 @@ "Common": { "ContinueButton": "Continue", "Available": "Available", + "PartiallyAvailable": "Partially Available", + "Monitored": "Monitored", "NotAvailable": "Not Available", "ProcessingRequest": "Processing Request", "PendingApproval": "Pending Approval", From 5df232f3f56e9a5fe5e71f30666c058a745a9e67 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 26 Aug 2018 10:10:58 +0100 Subject: [PATCH 345/495] got the view albums button working !wip --- src/Ombi.Api.Lidarr/ILidarrApi.cs | 4 +- src/Ombi.Api.Lidarr/LidarrApi.cs | 12 ++-- .../Models/AlbumByArtistResponse.cs | 51 +++++++++------- src/Ombi.Api.Lidarr/Models/AlbumResponse.cs | 27 +++++++++ src/Ombi.Api/Request.cs | 3 +- .../Rule/Search/ExistingRequestRuleTests.cs | 4 +- .../Engine/Interfaces/IMusicSearchEngine.cs | 2 +- src/Ombi.Core/Engine/MusicSearchEngine.cs | 47 +++++++++++++-- .../search/music/artistsearch.component.html | 2 +- .../search/music/artistsearch.component.ts | 58 ++++--------------- .../search/music/musicsearch.component.html | 6 +- .../app/search/music/musicsearch.component.ts | 9 ++- .../ClientApp/app/services/search.service.ts | 9 ++- src/Ombi/Controllers/SearchController.cs | 11 ++++ 14 files changed, 151 insertions(+), 94 deletions(-) create mode 100644 src/Ombi.Api.Lidarr/Models/AlbumResponse.cs diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs index 2fe74f803..d66f9716c 100644 --- a/src/Ombi.Api.Lidarr/ILidarrApi.cs +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -12,9 +12,9 @@ namespace Ombi.Api.Lidarr Task> GetRootFolders(string apiKey, string baseUrl); Task GetArtist(int artistId, string apiKey, string baseUrl); Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl); - Task GetAlbumsByArtist(int artistId, string apiKey, string baseUrl); + Task GetAlbumsByArtist(string foreignArtistId); Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl); Task> GetArtists(string apiKey, string baseUrl); - Task> GetAllAlbums(string apiKey, string baseUrl); + Task> GetAllAlbums(string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index 09b670afa..84e0d090a 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -82,12 +82,10 @@ namespace Ombi.Api.Lidarr return albums.FirstOrDefault(); } - public Task GetAlbumsByArtist(int artistId, string apiKey, string baseUrl) + public Task GetAlbumsByArtist(string foreignArtistId) { - var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); - - request.AddQueryString("artistId", artistId.ToString()); - AddHeaders(request, apiKey); + var request = new Request(string.Empty, $"https://api.lidarr.audio/api/v0.3/artist/{foreignArtistId}", + HttpMethod.Get) {IgnoreBaseUrlAppend = true}; return Api.Request(request); } @@ -99,12 +97,12 @@ namespace Ombi.Api.Lidarr return Api.Request>(request); } - public Task> GetAllAlbums(string apiKey, string baseUrl) + public Task> GetAllAlbums(string apiKey, string baseUrl) { var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); AddHeaders(request, apiKey); - return Api.Request>(request); + return Api.Request>(request); } private void AddHeaders(Request request, string key) diff --git a/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs b/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs index 0a54dbfeb..62f19651f 100644 --- a/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs +++ b/src/Ombi.Api.Lidarr/Models/AlbumByArtistResponse.cs @@ -1,27 +1,34 @@ -using System; - -namespace Ombi.Api.Lidarr.Models +namespace Ombi.Api.Lidarr.Models { public class AlbumByArtistResponse { - public string title { get; set; } - public string disambiguation { get; set; } - public int artistId { get; set; } - public string foreignAlbumId { get; set; } - public bool monitored { get; set; } - public int profileId { get; set; } - public int duration { get; set; } - public string albumType { get; set; } - public object[] secondaryTypes { get; set; } - public int mediumCount { get; set; } - public Ratings ratings { get; set; } - public DateTime releaseDate { get; set; } - public Currentrelease currentRelease { get; set; } - public Release[] releases { get; set; } - public object[] genres { get; set; } - public Medium[] media { get; set; } - public Image[] images { get; set; } - public Statistics statistics { get; set; } - public int id { get; set; } + public Album[] Albums { get; set; } + public string ArtistName { get; set; } + public string Disambiguation { get; set; } + public string Id { get; set; } + public Image[] Images { get; set; } + public Link[] Links { get; set; } + public string Overview { get; set; } + public Rating Rating { get; set; } + public string SortName { get; set; } + public string Status { get; set; } + public string Type { get; set; } + } + + public class Rating + { + public int Count { get; set; } + public decimal Value { get; set; } + } + + public class Album + { + public string Disambiguation { get; set; } + public string Id { get; set; } + public string ReleaseDate { get; set; } + public string[] ReleaseStatuses { get; set; } + public string[] SecondaryTypes { get; set; } + public string Title { get; set; } + public string Type { get; set; } } } \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs b/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs new file mode 100644 index 000000000..f9d35c43b --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/AlbumResponse.cs @@ -0,0 +1,27 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class AlbumResponse + { + public string title { get; set; } + public string disambiguation { get; set; } + public int artistId { get; set; } + public string foreignAlbumId { get; set; } + public bool monitored { get; set; } + public int profileId { get; set; } + public int duration { get; set; } + public string albumType { get; set; } + public object[] secondaryTypes { get; set; } + public int mediumCount { get; set; } + public Ratings ratings { get; set; } + public DateTime releaseDate { get; set; } + public Currentrelease currentRelease { get; set; } + public Release[] releases { get; set; } + public object[] genres { get; set; } + public Medium[] media { get; set; } + public Image[] images { get; set; } + public Statistics statistics { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api/Request.cs b/src/Ombi.Api/Request.cs index cfd284f54..fd888d0d2 100644 --- a/src/Ombi.Api/Request.cs +++ b/src/Ombi.Api/Request.cs @@ -28,6 +28,7 @@ namespace Ombi.Api public bool IgnoreErrors { get; set; } public bool Retry { get; set; } public List StatusCodeToRetry { get; set; } = new List(); + public bool IgnoreBaseUrlAppend { get; set; } public Action OnBeforeDeserialization { get; set; } @@ -38,7 +39,7 @@ namespace Ombi.Api var sb = new StringBuilder(); if (!string.IsNullOrEmpty(BaseUrl)) { - sb.Append(!BaseUrl.EndsWith("/") ? string.Format("{0}/", BaseUrl) : BaseUrl); + sb.Append(!BaseUrl.EndsWith("/") && !IgnoreBaseUrlAppend ? string.Format("{0}/", BaseUrl) : BaseUrl); } sb.Append(Endpoint.StartsWith("/") ? Endpoint.Remove(0, 1) : Endpoint); return sb.ToString(); diff --git a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs index a706472dd..e32c8e996 100644 --- a/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs +++ b/src/Ombi.Core.Tests/Rule/Search/ExistingRequestRuleTests.cs @@ -19,12 +19,14 @@ namespace Ombi.Core.Tests.Rule.Search MovieMock = new Mock(); TvMock = new Mock(); - Rule = new ExistingRule(MovieMock.Object, TvMock.Object); + MusicMock = new Mock(); + Rule = new ExistingRule(MovieMock.Object, TvMock.Object, MusicMock.Object); } private ExistingRule Rule { get; set; } private Mock MovieMock { get; set; } private Mock TvMock { get; set; } + private Mock MusicMock { get; set; } [Test] diff --git a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs index 44643bc9d..03294982a 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMusicSearchEngine.cs @@ -9,7 +9,7 @@ namespace Ombi.Core.Engine { Task GetAlbumArtist(string foreignArtistId); Task GetArtist(int artistId); - Task GetArtistAlbums(string foreignArtistId); + Task> GetArtistAlbums(string foreignArtistId); Task> SearchAlbum(string search); Task> SearchArtist(string search); } diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs index 7eba0429d..17f1aed54 100644 --- a/src/Ombi.Core/Engine/MusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -82,12 +82,21 @@ namespace Ombi.Core.Engine /// /// Returns all albums by the specified artist /// - /// + /// /// - public async Task GetArtistAlbums(string foreignArtistId) + public async Task> GetArtistAlbums(string foreignArtistId) { var settings = await GetSettings(); - return await _lidarrApi.GetArtistByForeignId(foreignArtistId, settings.ApiKey, settings.FullUri); + var result = await _lidarrApi.GetAlbumsByArtist(foreignArtistId); + // We do not want any Singles (This will include EP's) + var albumsOnly = + result.Albums.Where(x => !x.Type.Equals("Single", StringComparison.InvariantCultureIgnoreCase)); + var vm = new List(); + foreach (var album in albumsOnly) + { + vm.Add(await MapIntoAlbumVm(album, result.Id, result.ArtistName, settings)); + } + return vm; } /// @@ -121,7 +130,7 @@ namespace Ombi.Core.Engine Links = a.links, Overview = a.overview, }; - + var poster = a.images?.FirstOrDefault(x => x.coverType.Equals("poaster")); if (poster == null) { @@ -147,6 +156,7 @@ namespace Ombi.Core.Engine }; if (vm.Monitored) { + //TODO THEY HAVE FIXED THIS IN DEV // The JSON is different for some stupid reason // Need to lookup the artist now and all the images -.-" var artist = await _lidarrApi.GetArtist(a.artistId, settings.ApiKey, settings.FullUri); @@ -171,6 +181,35 @@ namespace Ombi.Core.Engine return vm; } + + private async Task MapIntoAlbumVm(Album a, string artistId, string artistName, LidarrSettings settings) + { + var fullAlbum = await _lidarrApi.GetAlbumByForeignId(a.Id, settings.ApiKey, settings.FullUri); + var vm = new SearchAlbumViewModel + { + ForeignAlbumId = a.Id, + Monitored = fullAlbum.monitored, + Rating = fullAlbum.ratings?.value ?? 0m, + ReleaseDate = fullAlbum.releaseDate, + Title = a.Title, + Disk = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, + ForeignArtistId = artistId, + ArtistName = artistName, + Cover = fullAlbum.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url + }; + + if (vm.Cover.IsNullOrEmpty()) + { + vm.Cover = fullAlbum.remoteCover; + } + + await Rules.StartSpecificRules(vm, SpecificRules.LidarrAlbum); + + await RunSearchRules(vm); + + return vm; + } + private LidarrSettings _settings; private async Task GetSettings() { diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.html b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html index b68990d69..5ea5433f1 100644 --- a/src/Ombi/ClientApp/app/search/music/artistsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html @@ -51,7 +51,7 @@ --> - diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts index 8794d5efc..76a223d09 100644 --- a/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts @@ -1,10 +1,7 @@ -import { Component, Input } from "@angular/core"; -import { TranslateService } from "@ngx-translate/core"; +import { Component, EventEmitter, Input, Output } from "@angular/core"; -import { AuthService } from "../../auth/auth.service"; -import { IRequestEngineResult, ISearchMovieResult } from "../../interfaces"; -import { ISearchArtistResult } from "../../interfaces/ISearchMusicResult"; -import { NotificationService, RequestService } from "../../services"; +import { ISearchAlbumResult, ISearchArtistResult } from "../../interfaces/ISearchMusicResult"; +import { SearchService } from "../../services"; @Component({ selector: "artist-search", @@ -13,51 +10,16 @@ import { NotificationService, RequestService } from "../../services"; export class ArtistSearchComponent { @Input() public result: ISearchArtistResult; - public engineResult: IRequestEngineResult; @Input() public defaultPoster: string; - constructor( - private requestService: RequestService, - private notificationService: NotificationService, private authService: AuthService, - private readonly translate: TranslateService) { - } - - public request(searchResult: ISearchMovieResult) { - searchResult.requested = true; - searchResult.requestProcessing = true; - searchResult.showSubscribe = false; - if (this.authService.hasRole("admin") || this.authService.hasRole("AutoApproveMovie")) { - searchResult.approved = true; - } - - try { - this.requestService.requestMovie({ theMovieDbId: searchResult.id }) - .subscribe(x => { - this.engineResult = x; + @Output() public viewAlbumsResult = new EventEmitter(); - if (this.engineResult.result) { - this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { - this.notificationService.success(x); - searchResult.processed = true; - }); - } else { - if (this.engineResult.errorMessage && this.engineResult.message) { - this.notificationService.warning("Request Added", `${this.engineResult.message} - ${this.engineResult.errorMessage}`); - } else { - this.notificationService.warning("Request Added", this.engineResult.message ? this.engineResult.message : this.engineResult.errorMessage); - } - searchResult.requested = false; - searchResult.approved = false; - searchResult.processed = false; - searchResult.requestProcessing = false; - - } - }); - } catch (e) { + constructor(private searchService: SearchService) { + } - searchResult.processed = false; - searchResult.requestProcessing = false; - this.notificationService.error(e); - } + public viewAllAlbums() { + this.searchService.getAlbumsForArtist(this.result.forignArtistId).subscribe(x => { + this.viewAlbumsResult.emit(x); + }); } } diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.html b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html index 867d7803e..b73ee553b 100644 --- a/src/Ombi/ClientApp/app/search/music/musicsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.html @@ -8,10 +8,10 @@
    - - + +
    @@ -29,7 +29,7 @@
    - +

    diff --git a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts index ad980ff60..e022ab6c3 100644 --- a/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/musicsearch.component.ts @@ -22,7 +22,7 @@ export class MusicSearchComponent implements OnInit { public albumResult: ISearchAlbumResult[]; public result: IRequestEngineResult; public searchApplied = false; - public searchAlbum: boolean = false; + public searchAlbum: boolean = true; @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; @@ -148,6 +148,13 @@ export class MusicSearchComponent implements OnInit { } } + public viewAlbumsForArtist(albums: ISearchAlbumResult[]) { + this.clearArtistResults(); + this.searchAlbum = true; + this.albumResult = albums; + this.setAlbumBackground(); + } + private clearArtistResults() { this.artistResult = []; this.searchApplied = false; diff --git a/src/Ombi/ClientApp/app/services/search.service.ts b/src/Ombi/ClientApp/app/services/search.service.ts index f6a5271f5..9769ad229 100644 --- a/src/Ombi/ClientApp/app/services/search.service.ts +++ b/src/Ombi/ClientApp/app/services/search.service.ts @@ -7,7 +7,7 @@ import { Observable } from "rxjs"; import { TreeNode } from "primeng/primeng"; import { ISearchMovieResult } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; -import { ISearchArtistResult } from "../interfaces/ISearchMusicResult"; +import { ISearchAlbumResult, ISearchArtistResult } from "../interfaces/ISearchMusicResult"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -73,7 +73,10 @@ export class SearchService extends ServiceHelpers { public searchArtist(searchTerm: string): Observable { return this.http.get(`${this.url}/Music/Artist/` + searchTerm); } - public searchAlbum(searchTerm: string): Observable { - return this.http.get(`${this.url}/Music/Album/` + searchTerm); + public searchAlbum(searchTerm: string): Observable { + return this.http.get(`${this.url}/Music/Album/` + searchTerm); + } + public getAlbumsForArtist(foreignArtistId: string): Observable { + return this.http.get(`${this.url}/Music/Artist/Album/${foreignArtistId}`); } } diff --git a/src/Ombi/Controllers/SearchController.cs b/src/Ombi/Controllers/SearchController.cs index 026e4541d..5d05ceae5 100644 --- a/src/Ombi/Controllers/SearchController.cs +++ b/src/Ombi/Controllers/SearchController.cs @@ -206,5 +206,16 @@ namespace Ombi.Controllers { return await MusicEngine.SearchAlbum(searchTerm); } + + /// + /// Returns all albums for the artist using the ForeignArtistId + /// + /// We use Lidarr as the Provider + /// + [HttpGet("music/artist/album/{foreignArtistId}")] + public async Task> GetAlbumsByArtist(string foreignArtistId) + { + return await MusicEngine.GetArtistAlbums(foreignArtistId); + } } } From 82d610e2357a30a2e2de7e0a55d5ffba2dfe6668 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 26 Aug 2018 10:32:08 +0100 Subject: [PATCH 346/495] Added the availablility checker #2313 !wip --- .../Agents/DiscordNotification.cs | 12 ++- .../Agents/EmailNotification.cs | 2 +- .../Agents/MattermostNotification.cs | 2 +- .../Agents/MobileNotification.cs | 2 +- .../Agents/PushbulletNotification.cs | 2 +- .../Agents/PushoverNotification.cs | 2 +- .../Agents/SlackNotification.cs | 2 +- .../Agents/TelegramNotification.cs | 2 +- src/Ombi.Notifications/BaseNotification.cs | 18 ++++- .../NotificationMessageCurlys.cs | 38 +++++++++- .../Jobs/Lidarr/LidarrAvailabilityChecker.cs | 73 +++++++++++++++++++ src/Ombi.Store/Entities/LidarrAlbumCache.cs | 5 ++ .../search/music/artistsearch.component.html | 2 +- .../search/music/artistsearch.component.ts | 2 + 14 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs diff --git a/src/Ombi.Notifications/Agents/DiscordNotification.cs b/src/Ombi.Notifications/Agents/DiscordNotification.cs index 66280ef70..d788b471c 100644 --- a/src/Ombi.Notifications/Agents/DiscordNotification.cs +++ b/src/Ombi.Notifications/Agents/DiscordNotification.cs @@ -20,8 +20,8 @@ namespace Ombi.Notifications.Agents { public DiscordNotification(IDiscordApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, - IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub) - : base(sn, r, m, t,s,log, sub) + IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub, IMusicRequestRepository music) + : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; @@ -130,12 +130,18 @@ namespace Ombi.Notifications.Agents title = MovieRequest.Title; image = MovieRequest.PosterPath; } - else + else if (model.RequestType == RequestType.TvShow) { user = TvRequest.RequestedUser.UserAlias; title = TvRequest.ParentRequest.Title; image = TvRequest.ParentRequest.PosterPath; } + else if (model.RequestType == RequestType.Album) + { + user = AlbumRequest.RequestedUser.UserAlias; + title = AlbumRequest.Title; + image = AlbumRequest.Cover; + } var message = $"Hello! The user '{user}' has requested {title} but it could not be added. This has been added into the requests queue and will keep retrying"; var notification = new NotificationMessage { diff --git a/src/Ombi.Notifications/Agents/EmailNotification.cs b/src/Ombi.Notifications/Agents/EmailNotification.cs index 53046ade0..3ab045e87 100644 --- a/src/Ombi.Notifications/Agents/EmailNotification.cs +++ b/src/Ombi.Notifications/Agents/EmailNotification.cs @@ -22,7 +22,7 @@ namespace Ombi.Notifications.Agents public class EmailNotification : BaseNotification, IEmailNotification { public EmailNotification(ISettingsService settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService c, - ILogger log, UserManager um, IRepository sub) : base(settings, r, m, t, c, log, sub) + ILogger log, UserManager um, IRepository sub, IMusicRequestRepository music) : base(settings, r, m, t, c, log, sub, music) { EmailProvider = prov; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MattermostNotification.cs b/src/Ombi.Notifications/Agents/MattermostNotification.cs index 8199d8bfe..9e8a34e3b 100644 --- a/src/Ombi.Notifications/Agents/MattermostNotification.cs +++ b/src/Ombi.Notifications/Agents/MattermostNotification.cs @@ -21,7 +21,7 @@ namespace Ombi.Notifications.Agents public class MattermostNotification : BaseNotification, IMattermostNotification { public MattermostNotification(IMattermostApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t,s,log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MobileNotification.cs b/src/Ombi.Notifications/Agents/MobileNotification.cs index 8559c043d..c521b99a4 100644 --- a/src/Ombi.Notifications/Agents/MobileNotification.cs +++ b/src/Ombi.Notifications/Agents/MobileNotification.cs @@ -22,7 +22,7 @@ namespace Ombi.Notifications.Agents { public MobileNotification(IOneSignalApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository notification, - UserManager um, IRepository sub) : base(sn, r, m, t, s,log, sub) + UserManager um, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { _api = api; _logger = log; diff --git a/src/Ombi.Notifications/Agents/PushbulletNotification.cs b/src/Ombi.Notifications/Agents/PushbulletNotification.cs index 24aa8cd22..0e488bf79 100644 --- a/src/Ombi.Notifications/Agents/PushbulletNotification.cs +++ b/src/Ombi.Notifications/Agents/PushbulletNotification.cs @@ -17,7 +17,7 @@ namespace Ombi.Notifications.Agents public class PushbulletNotification : BaseNotification, IPushbulletNotification { public PushbulletNotification(IPushbulletApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t,s,log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/PushoverNotification.cs b/src/Ombi.Notifications/Agents/PushoverNotification.cs index 5b82eb8a3..95b2a18d3 100644 --- a/src/Ombi.Notifications/Agents/PushoverNotification.cs +++ b/src/Ombi.Notifications/Agents/PushoverNotification.cs @@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents public class PushoverNotification : BaseNotification, IPushoverNotification { public PushoverNotification(IPushoverApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t, s, log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/SlackNotification.cs b/src/Ombi.Notifications/Agents/SlackNotification.cs index 894758591..6c04f5ea6 100644 --- a/src/Ombi.Notifications/Agents/SlackNotification.cs +++ b/src/Ombi.Notifications/Agents/SlackNotification.cs @@ -18,7 +18,7 @@ namespace Ombi.Notifications.Agents public class SlackNotification : BaseNotification, ISlackNotification { public SlackNotification(ISlackApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub) : base(sn, r, m, t, s, log, sub) + ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/TelegramNotification.cs b/src/Ombi.Notifications/Agents/TelegramNotification.cs index 827b0b590..7bcda7c7f 100644 --- a/src/Ombi.Notifications/Agents/TelegramNotification.cs +++ b/src/Ombi.Notifications/Agents/TelegramNotification.cs @@ -19,7 +19,7 @@ namespace Ombi.Notifications.Agents public TelegramNotification(ITelegramApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s - , IRepository sub) : base(sn, r, m, t,s,log, sub) + , IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t,s,log, sub, music) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/BaseNotification.cs index 507b8059b..287f86455 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/BaseNotification.cs @@ -19,7 +19,7 @@ namespace Ombi.Notifications.Interfaces public abstract class BaseNotification : INotification where T : Settings.Settings.Models.Settings, new() { protected BaseNotification(ISettingsService settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv, - ISettingsService customization, ILogger> log, IRepository sub) + ISettingsService customization, ILogger> log, IRepository sub, IMusicRequestRepository album) { Settings = settings; TemplateRepository = templateRepo; @@ -30,12 +30,14 @@ namespace Ombi.Notifications.Interfaces CustomizationSettings.ClearCache(); RequestSubscription = sub; _log = log; + AlbumRepository = album; } protected ISettingsService Settings { get; } protected INotificationTemplatesRepository TemplateRepository { get; } protected IMovieRequestRepository MovieRepository { get; } protected ITvRequestRepository TvRepository { get; } + protected IMusicRequestRepository AlbumRepository { get; } protected CustomizationSettings Customization { get; set; } protected IRepository RequestSubscription { get; set; } private ISettingsService CustomizationSettings { get; } @@ -43,6 +45,7 @@ namespace Ombi.Notifications.Interfaces protected ChildRequests TvRequest { get; set; } + protected AlbumRequest AlbumRequest { get; set; } protected MovieRequests MovieRequest { get; set; } protected IQueryable SubsribedUsers { get; private set; } @@ -130,10 +133,14 @@ namespace Ombi.Notifications.Interfaces { MovieRequest = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId); } - else + else if (type == RequestType.TvShow) { TvRequest = await TvRepository.GetChild().FirstOrDefaultAsync(x => x.Id == requestId); } + else if (type == RequestType.Album) + { + AlbumRequest = await AlbumRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId); + } } private async Task GetConfiguration() @@ -181,11 +188,16 @@ namespace Ombi.Notifications.Interfaces curlys.Setup(model, MovieRequest, Customization); } - else + else if (model.RequestType == RequestType.TvShow) { _log.LogDebug("Notification options: {@model}, Req: {@TvRequest}, Settings: {@Customization}", model, TvRequest, Customization); curlys.Setup(model, TvRequest, Customization); } + else if (model.RequestType == RequestType.Album) + { + _log.LogDebug("Notification options: {@model}, Req: {@AlbumRequest}, Settings: {@Customization}", model, AlbumRequest, Customization); + curlys.Setup(model, AlbumRequest, Customization); + } var parsed = resolver.ParseMessage(template, curlys); return parsed; diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 497c49c86..710f64619 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -47,14 +47,48 @@ namespace Ombi.Notifications if (req?.RequestType == RequestType.Movie) { - PosterImage = string.Format((req?.PosterPath ?? string.Empty).StartsWith("/", StringComparison.InvariantCultureIgnoreCase) + PosterImage = string.Format((req?.PosterPath ?? string.Empty).StartsWith("/", StringComparison.InvariantCultureIgnoreCase) ? "https://image.tmdb.org/t/p/w300{0}" : "https://image.tmdb.org/t/p/w300/{0}", req?.PosterPath); } else { PosterImage = req?.PosterPath; } - + + AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; + } + + public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s) + { + LoadIssues(opts); + string title; + if (req == null) + { + opts.Substitutes.TryGetValue("Title", out title); + } + else + { + title = req?.Title; + } + ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty; + ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName; + RequestedUser = req?.RequestedUser?.UserName; + if (UserName.IsNullOrEmpty()) + { + // Can be set if it's an issue + UserName = req?.RequestedUser?.UserName; + } + + Alias = (req?.RequestedUser?.Alias.HasValue() ?? false) ? req?.RequestedUser?.Alias : req?.RequestedUser?.UserName; + Title = title; + RequestedDate = req?.RequestedDate.ToString("D"); + if (Type.IsNullOrEmpty()) + { + Type = req?.RequestType.Humanize(); + } + Year = req?.ReleaseDate.Year.ToString(); + PosterImage = (req?.Cover.HasValue() ?? false) ? req.Cover : req?.Disk ?? string.Empty; + AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; } diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs new file mode 100644 index 000000000..b40eda06b --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Hangfire; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Ombi.Core.Notifications; +using Ombi.Helpers; +using Ombi.Notifications.Models; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public class LidarrAvailabilityChecker + { + public LidarrAvailabilityChecker(IMusicRequestRepository requests, IRepository albums, ILogger log, + IBackgroundJobClient job, INotificationService notification) + { + _cachedAlbums = albums; + _requestRepository = requests; + _logger = log; + _job = job; + _notificationService = notification; + } + + private readonly IMusicRequestRepository _requestRepository; + private readonly IRepository _cachedAlbums; + private readonly ILogger _logger; + private readonly IBackgroundJobClient _job; + private readonly INotificationService _notificationService; + + public async Task Start() + { + var allAlbumRequests = _requestRepository.GetAll().Include(x => x.RequestedUser).Where(x => !x.Available); + var albumsToUpdate = new List(); + foreach (var request in allAlbumRequests) + { + // Check if we have it cached + var cachedAlbum = await _cachedAlbums.FirstOrDefaultAsync(x => x.ForeignAlbumId.Equals(request.ForeignAlbumId)); + if (cachedAlbum != null) + { + if (cachedAlbum.Monitored && cachedAlbum.FullyAvailable) + { + request.Available = true; + request.MarkedAsAvailable = DateTime.Now; + albumsToUpdate.Add(request); + } + } + } + + foreach (var albumRequest in albumsToUpdate) + { + await _requestRepository.Update(albumRequest); + var recipient = albumRequest.RequestedUser.Email.HasValue() ? albumRequest.RequestedUser.Email : string.Empty; + + _logger.LogDebug("AlbumId: {0}, RequestUser: {1}", albumRequest.Id, recipient); + + _job.Enqueue(() => _notificationService.Publish(new NotificationOptions + { + DateTime = DateTime.Now, + NotificationType = NotificationType.RequestAvailable, + RequestId = albumRequest.Id, + RequestType = RequestType.Album, + Recipient = recipient, + })); + } + } + } +} \ No newline at end of file diff --git a/src/Ombi.Store/Entities/LidarrAlbumCache.cs b/src/Ombi.Store/Entities/LidarrAlbumCache.cs index 15d0a53da..d9ceab8a3 100644 --- a/src/Ombi.Store/Entities/LidarrAlbumCache.cs +++ b/src/Ombi.Store/Entities/LidarrAlbumCache.cs @@ -13,5 +13,10 @@ namespace Ombi.Store.Entities public bool Monitored { get; set; } public string Title { get; set; } public decimal PercentOfTracks { get; set; } + + [NotMapped] + public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; + [NotMapped] + public bool FullyAvailable => PercentOfTracks == 100; } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.html b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html index 5ea5433f1..77a68a841 100644 --- a/src/Ombi/ClientApp/app/search/music/artistsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.html @@ -51,7 +51,7 @@ --> - diff --git a/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts index 76a223d09..852e294e3 100644 --- a/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/artistsearch.component.ts @@ -11,6 +11,7 @@ export class ArtistSearchComponent { @Input() public result: ISearchArtistResult; @Input() public defaultPoster: string; + public searchingAlbums: boolean; @Output() public viewAlbumsResult = new EventEmitter(); @@ -18,6 +19,7 @@ export class ArtistSearchComponent { } public viewAllAlbums() { + this.searchingAlbums = true; this.searchService.getAlbumsForArtist(this.result.forignArtistId).subscribe(x => { this.viewAlbumsResult.emit(x); }); From cda7c0fe4c1ad6473aac476e05981587ee42e3e3 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 26 Aug 2018 13:59:10 +0100 Subject: [PATCH 347/495] !wip availability done --- src/Ombi.DependencyInjection/IocExtensions.cs | 1 + .../Jobs/Lidarr/ILidarrAvailabilityChecker.cs | 9 +++++++++ src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs | 10 +++++++++- .../Jobs/Lidarr/LidarrAvailabilityChecker.cs | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/Ombi.Schedule/Jobs/Lidarr/ILidarrAvailabilityChecker.cs diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index 52f3db919..c4b291936 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -189,6 +189,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } } } diff --git a/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAvailabilityChecker.cs new file mode 100644 index 000000000..f0c679229 --- /dev/null +++ b/src/Ombi.Schedule/Jobs/Lidarr/ILidarrAvailabilityChecker.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Ombi.Schedule.Jobs.Lidarr +{ + public interface ILidarrAvailabilityChecker + { + Task Start(); + } +} \ No newline at end of file diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs index 2eae36206..9708df589 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Hangfire; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.Extensions.Logging; @@ -19,12 +20,15 @@ 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, IOmbiContext ctx, + IBackgroundJobClient job, ILidarrAvailabilityChecker availability) { _lidarrSettings = lidarr; _lidarrApi = lidarrApi; _logger = log; _ctx = ctx; + _job = job; + _availability = availability; _lidarrSettings.ClearCache(); } @@ -32,6 +36,8 @@ namespace Ombi.Schedule.Jobs.Lidarr private readonly ILidarrApi _lidarrApi; private readonly ILogger _logger; private readonly IOmbiContext _ctx; + private readonly IBackgroundJobClient _job; + private readonly ILidarrAvailabilityChecker _availability; public async Task CacheContent() { @@ -74,6 +80,8 @@ namespace Ombi.Schedule.Jobs.Lidarr { _logger.LogError(LoggingEvents.Cacher, ex, "Failed caching queued items from Lidarr Album"); } + + _job.Enqueue(() => _availability.Start()); } } catch (Exception) diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs index b40eda06b..d5ba14a6d 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAvailabilityChecker.cs @@ -15,7 +15,7 @@ using Ombi.Store.Repository.Requests; namespace Ombi.Schedule.Jobs.Lidarr { - public class LidarrAvailabilityChecker + public class LidarrAvailabilityChecker : ILidarrAvailabilityChecker { public LidarrAvailabilityChecker(IMusicRequestRepository requests, IRepository albums, ILogger log, IBackgroundJobClient job, INotificationService notification) From 861e677151ab5804c3c0f9029a590e9012c2aac1 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Tue, 21 Aug 2018 22:54:08 +0100 Subject: [PATCH 348/495] Add methods to interface and add model class --- .../Engine/Interfaces/IMovieRequestEngine.cs | 2 -- src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs | 2 ++ src/Ombi.Core/Engine/MovieRequestEngine.cs | 9 +++++++++ src/Ombi.Core/Engine/TvRequestEngine.cs | 9 +++++++++ src/Ombi.Core/Models/RequestQuotaCountModel.cs | 11 +++++++++++ src/Ombi/Controllers/SearchController.cs | 13 +++++++++++++ 6 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/Ombi.Core/Models/RequestQuotaCountModel.cs diff --git a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs index 91cbb9e72..152a1d923 100644 --- a/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IMovieRequestEngine.cs @@ -17,7 +17,5 @@ namespace Ombi.Core.Engine.Interfaces Task ApproveMovie(MovieRequests request); Task ApproveMovieById(int requestId); Task DenyMovieById(int modelId); - - } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index 740428ec7..53111fd95 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using Ombi.Core.Models; using Ombi.Core.Models.Requests; using Ombi.Core.Models.UI; using Ombi.Store.Entities; @@ -22,5 +23,6 @@ namespace Ombi.Core.Engine.Interfaces Task GetTotal(); Task UnSubscribeRequest(int requestId, RequestType type); Task SubscribeToRequest(int requestId, RequestType type); + Task GetRemainingRequests(); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index f73c6fda1..7e0b61bd8 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -19,6 +19,7 @@ using Ombi.Core.Settings; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; +using Ombi.Core.Models; namespace Ombi.Core.Engine { @@ -483,5 +484,13 @@ namespace Ombi.Core.Engine return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"}; } + + public async Task GetRemainingRequests() + { + return new RequestQuotaCountModel() + { + HasLimit = false, + }; + } } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 90760f759..78a26f82a 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -23,6 +23,7 @@ using Ombi.Core.Settings; using Ombi.Settings.Settings.Models; using Ombi.Store.Entities.Requests; using Ombi.Store.Repository; +using Ombi.Core.Models; namespace Ombi.Core.Engine { @@ -612,5 +613,13 @@ namespace Ombi.Core.Engine return new RequestEngineResult { Result = true }; } + + public async Task GetRemainingRequests() + { + return new RequestQuotaCountModel() + { + HasLimit = false, + }; + } } } \ No newline at end of file diff --git a/src/Ombi.Core/Models/RequestQuotaCountModel.cs b/src/Ombi.Core/Models/RequestQuotaCountModel.cs new file mode 100644 index 000000000..23a692f41 --- /dev/null +++ b/src/Ombi.Core/Models/RequestQuotaCountModel.cs @@ -0,0 +1,11 @@ +namespace Ombi.Core.Models +{ + public class RequestQuotaCountModel + { + public bool HasLimit { get; set; } + + public int Limit { get; set; } + + public int Remaining { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Controllers/SearchController.cs b/src/Ombi/Controllers/SearchController.cs index fe5399d78..65e9f930b 100644 --- a/src/Ombi/Controllers/SearchController.cs +++ b/src/Ombi/Controllers/SearchController.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Ombi.Core; using Ombi.Core.Engine; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Models; using Ombi.Core.Models.Search; using StackExchange.Profiling; @@ -182,5 +183,17 @@ namespace Ombi.Controllers { return await TvEngine.Trending(); } + + [HttpGet("movie/requestCount")] + public async Task RemainingMovieRequests() + { + return null; + } + + [HttpGet("tv/requestCount")] + public async Task RemainingTvRequests() + { + return null; + } } } From 1bfbfdbe0033afa5ce25faa1b58878a317c46364 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Tue, 21 Aug 2018 22:54:55 +0100 Subject: [PATCH 349/495] Fix scss import for unix systems --- src/Ombi/ClientApp/styles/_imports.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi/ClientApp/styles/_imports.scss b/src/Ombi/ClientApp/styles/_imports.scss index 09da286f8..5b22e1ac4 100644 --- a/src/Ombi/ClientApp/styles/_imports.scss +++ b/src/Ombi/ClientApp/styles/_imports.scss @@ -1,2 +1,2 @@ -@import './styles.scss'; -@import './scrollbar.scss'; \ No newline at end of file +@import './Styles.scss'; +@import './scrollbar.scss'; From 7ef2a1679da4ce126c593999018bdf6c33ce6d26 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sat, 25 Aug 2018 01:20:36 +0100 Subject: [PATCH 350/495] Add dummy for request counter --- src/Ombi.Core/Engine/MovieRequestEngine.cs | 4 +++- .../app/interfaces/IRemainingRequests.ts | 5 +++++ .../app/requests/movierequests.component.ts | 4 ++++ .../app/search/moviesearch.component.html | 11 +++++++++-- .../app/search/moviesearch.component.ts | 16 ++++++++++++++-- .../ClientApp/app/services/request.service.ts | 5 +++++ src/Ombi/Controllers/RequestController.cs | 10 ++++++++++ 7 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 7e0b61bd8..a1c0b16cf 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -489,7 +489,9 @@ namespace Ombi.Core.Engine { return new RequestQuotaCountModel() { - HasLimit = false, + HasLimit = true, + Limit = 5, + Remaining = 4, }; } } diff --git a/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts b/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts new file mode 100644 index 000000000..774f3c5e6 --- /dev/null +++ b/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts @@ -0,0 +1,5 @@ +export interface IRemainingRequests { + hasLimit: boolean; + limit: number; + remaining: number; +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index db7b400db..9495f0790 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -7,6 +7,7 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { FilterType, IFilter, IIssueCategory, IMovieRequests, IPagenator, IRadarrProfile, IRadarrRootFolder, OrderType } from "../interfaces"; import { NotificationService, RadarrService, RequestService } from "../services"; +import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Component({ selector: "movie-requests", @@ -38,6 +39,8 @@ export class MovieRequestsComponent implements OnInit { public orderType: OrderType = OrderType.RequestedDateDesc; public OrderType = OrderType; + public remaining: IRemainingRequests; + public totalMovies: number = 100; private currentlyLoaded: number; private amountToLoad: number; @@ -80,6 +83,7 @@ export class MovieRequestsComponent implements OnInit { }; this.loadInit(); this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + } public paginate(event: IPagenator) { diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 44dc345bc..2e6458eb5 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -1,5 +1,6 @@ 
    +
    @@ -18,8 +19,14 @@
    -
    -
    + +

    + {{remaining.remaining}}/{{remaining.limit}} requests remaining. +

    + +
    +
    +
    diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 824308b21..dd2621ae1 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -8,6 +8,7 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; import { NotificationService, RequestService, SearchService } from "../services"; +import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Component({ selector: "movie-search", @@ -19,6 +20,7 @@ export class MovieSearchComponent implements OnInit { public searchChanged: Subject = new Subject(); public movieResults: ISearchMovieResult[]; public result: IRequestEngineResult; + public remaining: IRemainingRequests; public searchApplied = false; @Input() public issueCategories: IIssueCategory[]; @@ -35,7 +37,6 @@ export class MovieSearchComponent implements OnInit { private notificationService: NotificationService, private authService: AuthService, private readonly translate: TranslateService, private sanitizer: DomSanitizer, private readonly platformLocation: PlatformLocation) { - this.searchChanged.pipe( debounceTime(600), // Wait Xms after the last event before emitting last event distinctUntilChanged(), // only emit if value is different from previous value @@ -69,10 +70,21 @@ export class MovieSearchComponent implements OnInit { result: false, errorMessage: "", }; + this.remaining = { + hasLimit: false, + limit: 0, + remaining: 0, + }; + this.popularMovies(); - } + this.requestService.getRemainingMovieRequests().subscribe(remaining => { + this.remaining = remaining; + }); + + } public search(text: any) { + this.searchChanged.next(text.target.value); } diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 48fa5622d..5345369e7 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -8,6 +8,7 @@ import { TreeNode } from "primeng/primeng"; import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces"; import { ITvRequestViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; +import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Injectable() export class RequestService extends ServiceHelpers { @@ -15,6 +16,10 @@ export class RequestService extends ServiceHelpers { super(http, "/api/v1/Request/", platformLocation); } + public getRemainingMovieRequests(): Observable { + return this.http.get(`${this.url}movie/remaining`, {headers: this.headers}); + } + public requestMovie(movie: IMovieRequestModel): Observable { return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); } diff --git a/src/Ombi/Controllers/RequestController.cs b/src/Ombi/Controllers/RequestController.cs index d794f6001..71d09b2ef 100644 --- a/src/Ombi/Controllers/RequestController.cs +++ b/src/Ombi/Controllers/RequestController.cs @@ -12,6 +12,7 @@ using Ombi.Attributes; using Ombi.Core.Models.UI; using Ombi.Models; using Ombi.Store.Entities; +using Ombi.Core.Models; namespace Ombi.Controllers { @@ -464,5 +465,14 @@ namespace Ombi.Controllers await TvRequestEngine.UnSubscribeRequest(requestId, RequestType.TvShow); return true; } + + /// + /// Gets model containing remaining number of requests. + /// + [HttpGet("movie/remaining")] + public async Task GetRemainingRequests() + { + return await MovieRequestEngine.GetRemainingRequests(); + } } } \ No newline at end of file From 3959a79ea8702ceb4776290587e7ec8472f768c2 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 00:04:40 +0100 Subject: [PATCH 351/495] Move to seperate component and display for both TV and movies --- .vscode/launch.json | 46 ++++++++++++++++ .vscode/tasks.json | 15 +++++ src/Ombi.Core/Engine/MovieRequestEngine.cs | 3 +- src/Ombi.Core/Engine/TvRequestEngine.cs | 5 +- .../Models/RequestQuotaCountModel.cs | 4 ++ .../app/interfaces/IRemainingRequests.ts | 1 + .../requests/remainingrequests.component.html | 18 ++++++ .../requests/remainingrequests.component.ts | 55 +++++++++++++++++++ .../app/requests/remainingrequests.module.ts | 32 +++++++++++ .../app/search/moviesearch.component.html | 7 +-- .../app/search/moviesearch.component.ts | 13 +---- .../ClientApp/app/search/search.module.ts | 2 + .../app/search/tvsearch.component.html | 8 +-- .../ClientApp/app/services/request.service.ts | 4 ++ src/Ombi/Controllers/RequestController.cs | 11 +++- 15 files changed, 198 insertions(+), 26 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 src/Ombi/ClientApp/app/requests/remainingrequests.component.html create mode 100644 src/Ombi/ClientApp/app/requests/remainingrequests.component.ts create mode 100644 src/Ombi/ClientApp/app/requests/remainingrequests.module.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..3e98c769e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,46 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/Ombi/bin/Debug/netcoreapp2.1/Ombi.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Ombi", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart", + "launchBrowser": { + "enabled": false, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}" + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" + } + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ,] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..af3542206 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/Ombi/Ombi.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index a1c0b16cf..d40c3e7b8 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -489,9 +489,10 @@ namespace Ombi.Core.Engine { return new RequestQuotaCountModel() { - HasLimit = true, + HasLimit = false, Limit = 5, Remaining = 4, + NextRequest = DateTime.Parse("2018-08-27T00:00:00+01"), }; } } diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 78a26f82a..80e627ade 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -618,7 +618,10 @@ namespace Ombi.Core.Engine { return new RequestQuotaCountModel() { - HasLimit = false, + HasLimit = true, + Limit = 5, + Remaining = 4, + NextRequest = DateTime.Parse("2018-08-30T00:00:00+01"), }; } } diff --git a/src/Ombi.Core/Models/RequestQuotaCountModel.cs b/src/Ombi.Core/Models/RequestQuotaCountModel.cs index 23a692f41..1af9ad819 100644 --- a/src/Ombi.Core/Models/RequestQuotaCountModel.cs +++ b/src/Ombi.Core/Models/RequestQuotaCountModel.cs @@ -1,3 +1,5 @@ +using System; + namespace Ombi.Core.Models { public class RequestQuotaCountModel @@ -7,5 +9,7 @@ namespace Ombi.Core.Models public int Limit { get; set; } public int Remaining { get; set; } + + public DateTime NextRequest { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts b/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts index 774f3c5e6..ce1b37427 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts @@ -2,4 +2,5 @@ export interface IRemainingRequests { hasLimit: boolean; limit: number; remaining: number; + nextRequest: Date; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.html b/src/Ombi/ClientApp/app/requests/remainingrequests.component.html new file mode 100644 index 000000000..769cd79c2 --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.html @@ -0,0 +1,18 @@ +
    +

    + {{remaining.remaining}}/{{remaining.limit}} requests remaining +

    +

    + Another request will be added in {{daysUntil}} {{daysUntil == 1 ? "day" : "days"}} +

    +

    + Another request will be added in {{hoursUntil}} {{hoursUntil == 1 ? "hour" : "hours"}} +

    +

    + Another request will be added in {{minutesUntil}} {{minutesUntil == 1 ? "minute" : "minutes"}} +

    +
    + +
    +
    + diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts new file mode 100644 index 000000000..1279a60e9 --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -0,0 +1,55 @@ +import { Component, OnInit, Input } from "@angular/core"; +import { RequestService } from "../services"; +import { IRemainingRequests } from "../interfaces/IRemainingRequests"; + +@Component({ + selector: "remaining-requests", + templateUrl: "./remainingrequests.component.html", +}) + +export class RemainingRequestsComponent implements OnInit { + public remaining: IRemainingRequests; + @Input() public movie: boolean; + public daysUntil: number; + public hoursUntil: number; + public minutesUntil: number; + + constructor(private requestService: RequestService) + { + } + + ngOnInit(): void { + var self = this; + this.update(); + setInterval(function(){ + self.update() + }, 10000) + } + + update(): void { + var callback = (remaining => { + this.remaining = remaining; + this.daysUntil = Math.ceil(this.daysUntilNextRequest()); + this.hoursUntil = Math.ceil(this.hoursUntilNextRequest()); + this.minutesUntil = Math.ceil(this.minutesUntilNextRequest()) + }); + + if (this.movie) { + this.requestService.getRemainingMovieRequests().subscribe(callback); + } else { + this.requestService.getRemainingTvRequests().subscribe(callback); + } + } + + daysUntilNextRequest(): number { + return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60 / 60 / 24; + } + + hoursUntilNextRequest(): number { + return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60 / 60; + } + + minutesUntilNextRequest(): number { + return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60; + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts new file mode 100644 index 000000000..acbbed256 --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts @@ -0,0 +1,32 @@ +import { CommonModule } from "@angular/common"; +import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { RouterModule } from "@angular/router"; + +import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; + +import { SidebarModule, TooltipModule, TreeTableModule } from "primeng/primeng"; +import { RequestService } from "../services"; + +import { SharedModule } from "../shared/shared.module"; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + NgbModule.forRoot(), + TreeTableModule, + SharedModule, + SidebarModule, + TooltipModule, + ], + declarations: [ + ], + exports: [ + RouterModule, + ], + providers: [ + RequestService, + ], +}) +export class SearchModule { } diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 2e6458eb5..2e5a17559 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -20,12 +20,7 @@
    -

    - {{remaining.remaining}}/{{remaining.limit}} requests remaining. -

    - -
    -
    +
    diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index dd2621ae1..afd217089 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -8,7 +8,6 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; import { NotificationService, RequestService, SearchService } from "../services"; -import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Component({ selector: "movie-search", @@ -20,7 +19,7 @@ export class MovieSearchComponent implements OnInit { public searchChanged: Subject = new Subject(); public movieResults: ISearchMovieResult[]; public result: IRequestEngineResult; - public remaining: IRemainingRequests; + public searchApplied = false; @Input() public issueCategories: IIssueCategory[]; @@ -70,18 +69,8 @@ export class MovieSearchComponent implements OnInit { result: false, errorMessage: "", }; - this.remaining = { - hasLimit: false, - limit: 0, - remaining: 0, - }; this.popularMovies(); - - this.requestService.getRemainingMovieRequests().subscribe(remaining => { - this.remaining = remaining; - }); - } public search(text: any) { diff --git a/src/Ombi/ClientApp/app/search/search.module.ts b/src/Ombi/ClientApp/app/search/search.module.ts index 855207616..c2a309819 100644 --- a/src/Ombi/ClientApp/app/search/search.module.ts +++ b/src/Ombi/ClientApp/app/search/search.module.ts @@ -19,6 +19,7 @@ import { SearchService } from "../services"; import { AuthGuard } from "../auth/auth.guard"; import { SharedModule } from "../shared/shared.module"; +import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; const routes: Routes = [ { path: "", component: SearchComponent, canActivate: [AuthGuard] }, @@ -41,6 +42,7 @@ const routes: Routes = [ TvSearchComponent, SeriesInformationComponent, MovieSearchGridComponent, + RemainingRequestsComponent, ], exports: [ RouterModule, diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.html b/src/Ombi/ClientApp/app/search/tvsearch.component.html index 48e83578d..37453c97d 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.html +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.html @@ -26,15 +26,13 @@
    -
    -
    + + +
    - -
    -
    diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 5345369e7..4434b7839 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -20,6 +20,10 @@ export class RequestService extends ServiceHelpers { return this.http.get(`${this.url}movie/remaining`, {headers: this.headers}); } + public getRemainingTvRequests(): Observable { + return this.http.get(`${this.url}tv/remaining`, {headers: this.headers}); + } + public requestMovie(movie: IMovieRequestModel): Observable { return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); } diff --git a/src/Ombi/Controllers/RequestController.cs b/src/Ombi/Controllers/RequestController.cs index 71d09b2ef..f2412727f 100644 --- a/src/Ombi/Controllers/RequestController.cs +++ b/src/Ombi/Controllers/RequestController.cs @@ -470,9 +470,18 @@ namespace Ombi.Controllers /// Gets model containing remaining number of requests. ///
    [HttpGet("movie/remaining")] - public async Task GetRemainingRequests() + public async Task GetRemainingMovieRequests() { return await MovieRequestEngine.GetRemainingRequests(); } + + /// + /// Gets model containing remaining number of requests. + /// + [HttpGet("tv/remaining")] + public async Task GetRemainingTvRequests() + { + return await TvRequestEngine.GetRemainingRequests(); + } } } \ No newline at end of file From 6ebe2deb52c12e16e33ccec1102a46b2ebc0738b Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 00:43:18 +0100 Subject: [PATCH 352/495] Add logic for retriving request information --- src/Ombi.Core/Engine/TvRequestEngine.cs | 29 ++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 80e627ade..da61b018a 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -616,12 +616,35 @@ namespace Ombi.Core.Engine public async Task GetRemainingRequests() { + OmbiUser user = await GetUser(); + int limit = user.EpisodeRequestLimit ?? 0; + + if (limit <= 0) + { + return new RequestQuotaCountModel() + { + HasLimit = false, + Limit = 0, + Remaining = 0, + NextRequest = DateTime.Now, + }; + } + + IQueryable log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.TvShow); + + int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + + DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)) + .OrderBy(x => x.RequestDate) + .Select(x => x.RequestDate) + .FirstOrDefaultAsync(); + return new RequestQuotaCountModel() { HasLimit = true, - Limit = 5, - Remaining = 4, - NextRequest = DateTime.Parse("2018-08-30T00:00:00+01"), + Limit = limit, + Remaining = count, + NextRequest = oldestRequestedAt.AddDays(7), }; } } From 0e46e66a6d50400e03c409d292de2ba8027923b6 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 00:50:05 +0100 Subject: [PATCH 353/495] Add logic for movie request count --- src/Ombi.Core/Engine/MovieRequestEngine.cs | 31 +++++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index d40c3e7b8..5cea34627 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -487,12 +487,35 @@ namespace Ombi.Core.Engine public async Task GetRemainingRequests() { + OmbiUser user = await GetUser(); + int limit = user.MovieRequestLimit ?? 0; + + if (limit <= 0) + { + return new RequestQuotaCountModel() + { + HasLimit = false, + Limit = 0, + Remaining = 0, + NextRequest = DateTime.Now, + }; + } + + IQueryable log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.Movie); + + int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + + DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)) + .OrderBy(x => x.RequestDate) + .Select(x => x.RequestDate) + .FirstOrDefaultAsync(); + return new RequestQuotaCountModel() { - HasLimit = false, - Limit = 5, - Remaining = 4, - NextRequest = DateTime.Parse("2018-08-27T00:00:00+01"), + HasLimit = true, + Limit = limit, + Remaining = count, + NextRequest = oldestRequestedAt.AddDays(7), }; } } From b30a2c09501b05c198221768291961649a8a6777 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 01:34:44 +0100 Subject: [PATCH 354/495] Trigger update of request limit on new request --- .../app/requests/remainingrequests.component.ts | 17 +++++++++++++---- .../app/search/moviesearch.component.html | 2 +- .../app/search/moviesearch.component.ts | 6 ++++-- .../app/search/seriesinformation.component.ts | 3 ++- .../app/search/tvsearch.component.html | 2 +- .../ClientApp/app/search/tvsearch.component.ts | 5 ++++- 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts index 1279a60e9..39f6ca200 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -21,17 +21,20 @@ export class RemainingRequestsComponent implements OnInit { ngOnInit(): void { var self = this; this.update(); + setInterval(function(){ - self.update() + self.calculateTime(); }, 10000) + + setInterval(function(){ + self.update() + }, 60000) } update(): void { var callback = (remaining => { this.remaining = remaining; - this.daysUntil = Math.ceil(this.daysUntilNextRequest()); - this.hoursUntil = Math.ceil(this.hoursUntilNextRequest()); - this.minutesUntil = Math.ceil(this.minutesUntilNextRequest()) + this.calculateTime(); }); if (this.movie) { @@ -41,6 +44,12 @@ export class RemainingRequestsComponent implements OnInit { } } + calculateTime(): void { + this.daysUntil = Math.ceil(this.daysUntilNextRequest()); + this.hoursUntil = Math.ceil(this.hoursUntilNextRequest()); + this.minutesUntil = Math.ceil(this.minutesUntilNextRequest()) + } + daysUntilNextRequest(): number { return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60 / 60 / 24; } diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 2e5a17559..6d8c040ac 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -20,7 +20,7 @@ - +
    diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index afd217089..a6a009962 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -1,5 +1,5 @@ import { PlatformLocation } from "@angular/common"; -import { Component, Input, OnInit } from "@angular/core"; +import { Component, Input, OnInit, ViewChild } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; @@ -8,6 +8,7 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; import { NotificationService, RequestService, SearchService } from "../services"; +import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; @Component({ selector: "movie-search", @@ -24,6 +25,7 @@ export class MovieSearchComponent implements OnInit { @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; + @ViewChild('remainingFilms') public remainingRequestsComponent: RemainingRequestsComponent; public issuesBarVisible = false; public issueRequestTitle: string; public issueRequestId: number; @@ -89,7 +91,7 @@ export class MovieSearchComponent implements OnInit { this.requestService.requestMovie({ theMovieDbId: searchResult.id }) .subscribe(x => { this.result = x; - + this.remainingRequestsComponent.update(); if (this.result.result) { this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { this.notificationService.success(x); diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts index cc8701a5b..27cdaf684 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit} from "@angular/core"; +import { Component, Input, OnInit, ViewChild} from "@angular/core"; import { NotificationService } from "../services"; import { RequestService } from "../services"; @@ -7,6 +7,7 @@ import { SearchService } from "../services"; import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; import { IEpisodesRequests } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; +import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; @Component({ selector: "seriesinformation", diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.html b/src/Ombi/ClientApp/app/search/tvsearch.component.html index 37453c97d..5f4938cf1 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.html +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.html @@ -27,7 +27,7 @@
    - +
    diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index a41f34586..b18b1343a 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -1,5 +1,5 @@ import { PlatformLocation } from "@angular/common"; -import { Component, Input, OnInit } from "@angular/core"; +import { Component, Input, OnInit, ViewChild } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; import { Subject } from "rxjs"; import { debounceTime, distinctUntilChanged } from "rxjs/operators"; @@ -7,6 +7,7 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { IIssueCategory, IRequestEngineResult, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; import { ImageService, NotificationService, RequestService, SearchService } from "../services"; +import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; @Component({ selector: "tv-search", @@ -24,6 +25,7 @@ export class TvSearchComponent implements OnInit { @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; + @ViewChild('remainingTvShows') public remainingRequestsComponent: RemainingRequestsComponent; public issuesBarVisible = false; public issueRequestTitle: string; public issueRequestId: number; @@ -162,6 +164,7 @@ export class TvSearchComponent implements OnInit { this.requestService.requestTv(viewModel) .subscribe(x => { this.result = x; + this.remainingRequestsComponent.update(); if (this.result.result) { this.notificationService.success( `Request for ${searchResult.title} has been added successfully`); From c5e396a9e846c71fbf1b4dd5dd77b44bcf27f54b Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 14:19:37 +0100 Subject: [PATCH 355/495] Fix issues with remaining count updating --- .../requests/remainingrequests.component.ts | 4 +++ .../app/search/moviesearch.component.ts | 5 +--- .../app/search/seriesinformation.component.ts | 3 +-- .../app/search/tvsearch.component.ts | 5 +--- .../ClientApp/app/services/request.service.ts | 25 ++++++++++++++++--- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts index 39f6ca200..9e64b0013 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -22,6 +22,10 @@ export class RemainingRequestsComponent implements OnInit { var self = this; this.update(); + this.requestService.onRequested().subscribe(m => { + this.update(); + }); + setInterval(function(){ self.calculateTime(); }, 10000) diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index a6a009962..965837bae 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -1,5 +1,5 @@ import { PlatformLocation } from "@angular/common"; -import { Component, Input, OnInit, ViewChild } from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; import { TranslateService } from "@ngx-translate/core"; import { Subject } from "rxjs"; @@ -8,7 +8,6 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces"; import { NotificationService, RequestService, SearchService } from "../services"; -import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; @Component({ selector: "movie-search", @@ -25,7 +24,6 @@ export class MovieSearchComponent implements OnInit { @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; - @ViewChild('remainingFilms') public remainingRequestsComponent: RemainingRequestsComponent; public issuesBarVisible = false; public issueRequestTitle: string; public issueRequestId: number; @@ -91,7 +89,6 @@ export class MovieSearchComponent implements OnInit { this.requestService.requestMovie({ theMovieDbId: searchResult.id }) .subscribe(x => { this.result = x; - this.remainingRequestsComponent.update(); if (this.result.result) { this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { this.notificationService.success(x); diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts index 27cdaf684..5c0088268 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, ViewChild} from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; import { NotificationService } from "../services"; import { RequestService } from "../services"; @@ -7,7 +7,6 @@ import { SearchService } from "../services"; import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; import { IEpisodesRequests } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; -import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; @Component({ selector: "seriesinformation", diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index b18b1343a..a41f34586 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -1,5 +1,5 @@ import { PlatformLocation } from "@angular/common"; -import { Component, Input, OnInit, ViewChild } from "@angular/core"; +import { Component, Input, OnInit } from "@angular/core"; import { DomSanitizer } from "@angular/platform-browser"; import { Subject } from "rxjs"; import { debounceTime, distinctUntilChanged } from "rxjs/operators"; @@ -7,7 +7,6 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { IIssueCategory, IRequestEngineResult, ISearchTvResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; import { ImageService, NotificationService, RequestService, SearchService } from "../services"; -import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; @Component({ selector: "tv-search", @@ -25,7 +24,6 @@ export class TvSearchComponent implements OnInit { @Input() public issueCategories: IIssueCategory[]; @Input() public issuesEnabled: boolean; - @ViewChild('remainingTvShows') public remainingRequestsComponent: RemainingRequestsComponent; public issuesBarVisible = false; public issueRequestTitle: string; public issueRequestId: number; @@ -164,7 +162,6 @@ export class TvSearchComponent implements OnInit { this.requestService.requestTv(viewModel) .subscribe(x => { this.result = x; - this.remainingRequestsComponent.update(); if (this.result.result) { this.notificationService.success( `Request for ${searchResult.title} has been added successfully`); diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 4434b7839..7940c43d6 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -2,7 +2,7 @@ import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable } from "rxjs"; +import { Observable, ReplaySubject } from "rxjs"; import { TreeNode } from "primeng/primeng"; import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces"; @@ -12,10 +12,15 @@ import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Injectable() export class RequestService extends ServiceHelpers { + private requestEvents = new ReplaySubject(); constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/Request/", platformLocation); } + public onRequested(): Observable { + return this.requestEvents.asObservable(); + } + public getRemainingMovieRequests(): Observable { return this.http.get(`${this.url}movie/remaining`, {headers: this.headers}); } @@ -25,7 +30,14 @@ export class RequestService extends ServiceHelpers { } public requestMovie(movie: IMovieRequestModel): Observable { - return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); + var observer = Observable.create(observer => { + this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}).subscribe(m => { + observer.next(m); + this.requestEvents.next(m); + }); + }); + + return observer; } public getTotalMovies(): Observable { @@ -37,7 +49,14 @@ export class RequestService extends ServiceHelpers { } public requestTv(tv: ITvRequestViewModel): Observable { - return this.http.post(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers}); + var observer = Observable.create(observer => { + return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }).subscribe(m => { + observer.next(m); + this.requestEvents.next(m); + }); + }); + + return observer; } public approveMovie(movie: IMovieUpdateModel): Observable { From b7e5e3dfb4b1fc1d727a16b06565e3138b2ca44c Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 17:34:00 +0100 Subject: [PATCH 356/495] Fix bug with TV requests in which requesting a seasion would treat request as single episode --- src/Ombi.Core/Engine/TvRequestEngine.cs | 8 +++++++- src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs | 9 +++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index da61b018a..939b0fe3e 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -609,6 +609,7 @@ namespace Ombi.Core.Engine RequestDate = DateTime.UtcNow, RequestId = model.Id, RequestType = RequestType.TvShow, + EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(), }); return new RequestEngineResult { Result = true }; @@ -632,7 +633,12 @@ namespace Ombi.Core.Engine IQueryable log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.TvShow); - int count = limit - await log.CountAsync(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); + // Needed, due to a bug which would cause all episode counts to be 0 + int zeroEpisodeCount = await log.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync(); + + int episodeCount = await log.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync(); + + int count = limit - (zeroEpisodeCount + episodeCount); DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)) .OrderBy(x => x.RequestDate) diff --git a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs index a19ac1df8..4d5fcf8e2 100644 --- a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs @@ -88,8 +88,13 @@ namespace Ombi.Core.Rule.Rules.Request // Count how many requests in the past 7 days var tv = tvLogs.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)); - var count = await tv.Select(x => x.EpisodeCount).CountAsync(); - count += requestCount; // Add the amount of requests in + + // Needed, due to a bug which would cause all episode counts to be 0 + var zeroEpisodeCount = await tv.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync(); + + var episodeCount = await tv.Where(x => x.EpisodeCount != 0).Select(x => x.EpisodeCount).SumAsync(); + + var count = requestCount + episodeCount + zeroEpisodeCount; // Add the amount of requests in if (count > episodeLimit) { return Fail("You have exceeded your Episode request quota!"); From 0b620e9ad4000db5d903544248dad05944f8a12c Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 17:42:28 +0100 Subject: [PATCH 357/495] Fix bug when submitting requests for multiple episodes accross multiple seasons --- src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs index 4d5fcf8e2..64cd6c076 100644 --- a/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs +++ b/src/Ombi.Core/Rule/Rules/Request/RequestLimitRule.cs @@ -81,7 +81,7 @@ namespace Ombi.Core.Rule.Rules.Request // Get the count of requests to be made foreach (var s in child.SeasonRequests) { - requestCount = s.Episodes.Count; + requestCount += s.Episodes.Count; } var tvLogs = requestLog.Where(x => x.RequestType == RequestType.TvShow); From 62416e3538b7f170a3a491bc78283c2c00fbdd57 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 18:49:33 +0100 Subject: [PATCH 358/495] Remove local vscode files --- .vscode/launch.json | 46 --------------------------------------------- .vscode/tasks.json | 15 --------------- 2 files changed, 61 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/tasks.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 3e98c769e..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": ".NET Core Launch (web)", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/Ombi/bin/Debug/netcoreapp2.1/Ombi.dll", - "args": [], - "cwd": "${workspaceFolder}/src/Ombi", - "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart", - "launchBrowser": { - "enabled": false, - "args": "${auto-detect-url}", - "windows": { - "command": "cmd.exe", - "args": "/C start ${auto-detect-url}" - }, - "osx": { - "command": "open" - }, - "linux": { - "command": "xdg-open" - } - }, - "env": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "sourceFileMap": { - "/Views": "${workspaceFolder}/Views" - } - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach", - "processId": "${command:pickProcess}" - } - ,] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index af3542206..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/src/Ombi/Ombi.csproj" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file From d12e46b45103991370a46decd80796e0a59bc7ac Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 18:53:08 +0100 Subject: [PATCH 359/495] Remove unused methods from SearchController --- src/Ombi/Controllers/SearchController.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Ombi/Controllers/SearchController.cs b/src/Ombi/Controllers/SearchController.cs index 65e9f930b..0c3c33244 100644 --- a/src/Ombi/Controllers/SearchController.cs +++ b/src/Ombi/Controllers/SearchController.cs @@ -183,17 +183,5 @@ namespace Ombi.Controllers { return await TvEngine.Trending(); } - - [HttpGet("movie/requestCount")] - public async Task RemainingMovieRequests() - { - return null; - } - - [HttpGet("tv/requestCount")] - public async Task RemainingTvRequests() - { - return null; - } } } From c6bb864be758b6f92baa0005bbc3650c8a5be513 Mon Sep 17 00:00:00 2001 From: David Pooley Date: Sun, 26 Aug 2018 20:57:39 +0100 Subject: [PATCH 360/495] Allow for the ability to set Pushover notification sound and priority from within Ombi. --- src/Ombi.Api.Pushover/IPushoverApi.cs | 2 +- src/Ombi.Api.Pushover/PushoverApi.cs | 4 +- .../Agents/PushoverNotification.cs | 2 +- .../Models/Notifications/PushoverSettings.cs | 2 + .../app/interfaces/INotificationSettings.ts | 2 + .../notifications/pushover.component.html | 42 +++++++++++++++++++ .../notifications/pushover.component.ts | 2 + 7 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/Ombi.Api.Pushover/IPushoverApi.cs b/src/Ombi.Api.Pushover/IPushoverApi.cs index 42e8e9060..554a15b60 100644 --- a/src/Ombi.Api.Pushover/IPushoverApi.cs +++ b/src/Ombi.Api.Pushover/IPushoverApi.cs @@ -5,6 +5,6 @@ namespace Ombi.Api.Pushover { public interface IPushoverApi { - Task PushAsync(string accessToken, string message, string userToken); + Task PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound); } } \ No newline at end of file diff --git a/src/Ombi.Api.Pushover/PushoverApi.cs b/src/Ombi.Api.Pushover/PushoverApi.cs index fd6ca23ea..9f91bc7ca 100644 --- a/src/Ombi.Api.Pushover/PushoverApi.cs +++ b/src/Ombi.Api.Pushover/PushoverApi.cs @@ -16,13 +16,13 @@ namespace Ombi.Api.Pushover private readonly IApi _api; private const string PushoverEndpoint = "https://api.pushover.net/1"; - public async Task PushAsync(string accessToken, string message, string userToken) + public async Task PushAsync(string accessToken, string message, string userToken, sbyte priority, string sound) { if (message.Contains("'")) { message = message.Replace("'", "'"); } - var request = new Request($"messages.json?token={accessToken}&user={userToken}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post); + var request = new Request($"messages.json?token={accessToken}&user={userToken}&priority={priority}&sound={sound}&message={WebUtility.HtmlEncode(message)}", PushoverEndpoint, HttpMethod.Post); var result = await _api.Request(request); return result; diff --git a/src/Ombi.Notifications/Agents/PushoverNotification.cs b/src/Ombi.Notifications/Agents/PushoverNotification.cs index 5b82eb8a3..2719c3861 100644 --- a/src/Ombi.Notifications/Agents/PushoverNotification.cs +++ b/src/Ombi.Notifications/Agents/PushoverNotification.cs @@ -177,7 +177,7 @@ namespace Ombi.Notifications.Agents { try { - await Api.PushAsync(settings.AccessToken, model.Message, settings.UserToken); + await Api.PushAsync(settings.AccessToken, model.Message, settings.UserToken, settings.Priority, settings.Sound); } catch (Exception e) { diff --git a/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs index d845e8695..3e65d628d 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs @@ -8,5 +8,7 @@ namespace Ombi.Settings.Settings.Models.Notifications public bool Enabled { get; set; } public string AccessToken { get; set; } public string UserToken { get; set; } + public sbyte Priority { get; set; } + public string Sound { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts index 8c5fcd5bc..fc8b89f1e 100644 --- a/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/INotificationSettings.ts @@ -88,6 +88,8 @@ export interface IPushoverNotificationSettings extends INotificationSettings { accessToken: string; notificationTemplates: INotificationTemplates[]; userToken: string; + priority: number; + sound: string; } export interface IMattermostNotifcationSettings extends INotificationSettings { diff --git a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html index b56467c9f..499263dec 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html +++ b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.html @@ -28,6 +28,48 @@
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts index 5d208568c..64f339192 100644 --- a/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts +++ b/src/Ombi/ClientApp/app/settings/notifications/pushover.component.ts @@ -27,6 +27,8 @@ export class PushoverComponent implements OnInit { enabled: [x.enabled], userToken: [x.userToken], accessToken: [x.accessToken, [Validators.required]], + priority: [x.priority], + sound: [x.sound], }); }); } From e770d321eb2b319f2e93215e2d04c98e18ab47a4 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sun, 26 Aug 2018 21:02:23 +0100 Subject: [PATCH 361/495] Fix lint errors --- .../app/interfaces/IRemainingRequests.ts | 2 +- .../app/requests/movierequests.component.ts | 2 +- .../requests/remainingrequests.component.ts | 39 ++++++++++--------- .../ClientApp/app/search/search.module.ts | 2 +- .../ClientApp/app/services/request.service.ts | 5 ++- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts b/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts index ce1b37427..a6ff1a66c 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRemainingRequests.ts @@ -3,4 +3,4 @@ export interface IRemainingRequests { limit: number; remaining: number; nextRequest: Date; -} \ No newline at end of file +} diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index 9495f0790..651637308 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -6,8 +6,8 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { FilterType, IFilter, IIssueCategory, IMovieRequests, IPagenator, IRadarrProfile, IRadarrRootFolder, OrderType } from "../interfaces"; -import { NotificationService, RadarrService, RequestService } from "../services"; import { IRemainingRequests } from "../interfaces/IRemainingRequests"; +import { NotificationService, RadarrService, RequestService } from "../services"; @Component({ selector: "movie-requests", diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts index 9e64b0013..4f4439c13 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -1,6 +1,7 @@ -import { Component, OnInit, Input } from "@angular/core"; +import { IRemainingRequests } from "../interfaces/IRemainingRequests"; import { RequestService } from "../services"; -import { IRemainingRequests } from "../interfaces/IRemainingRequests"; + +import { Component, Input, OnInit } from "@angular/core"; @Component({ selector: "remaining-requests", @@ -14,29 +15,29 @@ export class RemainingRequestsComponent implements OnInit { public hoursUntil: number; public minutesUntil: number; - constructor(private requestService: RequestService) - { + constructor(private requestService: RequestService) { } - ngOnInit(): void { - var self = this; + public ngOnInit() { + const self = this; + this.update(); this.requestService.onRequested().subscribe(m => { this.update(); }); - setInterval(function(){ + setInterval(() => { self.calculateTime(); - }, 10000) + }, 10000); - setInterval(function(){ - self.update() - }, 60000) + setInterval(() => { + self.update(); + }, 60000); } - update(): void { - var callback = (remaining => { + public update(): void { + const callback = (remaining => { this.remaining = remaining; this.calculateTime(); }); @@ -48,21 +49,21 @@ export class RemainingRequestsComponent implements OnInit { } } - calculateTime(): void { + private calculateTime(): void { this.daysUntil = Math.ceil(this.daysUntilNextRequest()); this.hoursUntil = Math.ceil(this.hoursUntilNextRequest()); - this.minutesUntil = Math.ceil(this.minutesUntilNextRequest()) + this.minutesUntil = Math.ceil(this.minutesUntilNextRequest()); } - daysUntilNextRequest(): number { + private daysUntilNextRequest(): number { return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60 / 60 / 24; } - hoursUntilNextRequest(): number { + private hoursUntilNextRequest(): number { return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60 / 60; } - minutesUntilNextRequest(): number { + private minutesUntilNextRequest(): number { return (new Date(this.remaining.nextRequest).getTime() - new Date().getTime()) / 1000 / 60; } -} \ No newline at end of file +} diff --git a/src/Ombi/ClientApp/app/search/search.module.ts b/src/Ombi/ClientApp/app/search/search.module.ts index c2a309819..75c2de5d9 100644 --- a/src/Ombi/ClientApp/app/search/search.module.ts +++ b/src/Ombi/ClientApp/app/search/search.module.ts @@ -18,8 +18,8 @@ import { SearchService } from "../services"; import { AuthGuard } from "../auth/auth.guard"; -import { SharedModule } from "../shared/shared.module"; import { RemainingRequestsComponent } from "../requests/remainingrequests.component"; +import { SharedModule } from "../shared/shared.module"; const routes: Routes = [ { path: "", component: SearchComponent, canActivate: [AuthGuard] }, diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 7940c43d6..a7fbc76bf 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -8,6 +8,7 @@ import { TreeNode } from "primeng/primeng"; import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces"; import { ITvRequestViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; + import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Injectable() @@ -30,7 +31,7 @@ export class RequestService extends ServiceHelpers { } public requestMovie(movie: IMovieRequestModel): Observable { - var observer = Observable.create(observer => { + const observer = Observable.create(observer => { this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}).subscribe(m => { observer.next(m); this.requestEvents.next(m); @@ -49,7 +50,7 @@ export class RequestService extends ServiceHelpers { } public requestTv(tv: ITvRequestViewModel): Observable { - var observer = Observable.create(observer => { + const observer = Observable.create(observer => { return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }).subscribe(m => { observer.next(m); this.requestEvents.next(m); From 067ffae3031601cc8018c880c49a9bc87baeaccf Mon Sep 17 00:00:00 2001 From: David Pooley Date: Sun, 26 Aug 2018 22:48:46 +0100 Subject: [PATCH 362/495] Add default values for Priority and Sound. --- .../Settings/Models/Notifications/PushoverSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs index 3e65d628d..9c3dfc350 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/PushoverSettings.cs @@ -8,7 +8,7 @@ namespace Ombi.Settings.Settings.Models.Notifications public bool Enabled { get; set; } public string AccessToken { get; set; } public string UserToken { get; set; } - public sbyte Priority { get; set; } - public string Sound { get; set; } + public sbyte Priority { get; set; } = 0; + public string Sound { get; set; } = "pushover"; } } \ No newline at end of file From b72905ab4aecc1af30d124806bfad240db3fa3f3 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 26 Aug 2018 23:12:21 +0100 Subject: [PATCH 363/495] Got most of the requesting stuff done! !wip #2313 --- src/Ombi.Api.Lidarr/ILidarrApi.cs | 5 + src/Ombi.Api.Lidarr/LidarrApi.cs | 46 ++- src/Ombi.Api.Lidarr/Models/ArtistAdd.cs | 48 +++ .../Models/LanguageProfiles.cs | 8 + src/Ombi.Api.Lidarr/Models/MetadataProfile.cs | 8 + src/Ombi.Core/Engine/MusicRequestEngine.cs | 48 +-- src/Ombi.Core/Senders/IMusicSender.cs | 10 + src/Ombi.Core/Senders/MusicSender.cs | 110 ++++++ src/Ombi.DependencyInjection/IocExtensions.cs | 1 + src/Ombi.Helpers/StringHelper.cs | 5 + .../Models/External/LidarrSettings.cs | 3 + src/Ombi/ClientApp/app/interfaces/IRadarr.ts | 5 + .../ClientApp/app/interfaces/IRequestModel.ts | 3 +- .../ClientApp/app/interfaces/ISettings.ts | 3 + .../music/musicrequests.component.html | 270 ++++++++++++++ .../requests/music/musicrequests.component.ts | 350 ++++++++++++++++++ .../app/requests/request.component.html | 7 + .../app/requests/request.component.ts | 9 + .../ClientApp/app/requests/requests.module.ts | 3 +- .../services/applications/lidarr.service.ts | 9 +- .../ClientApp/app/services/request.service.ts | 4 +- .../app/settings/lidarr/lidarr.component.html | 49 ++- .../app/settings/lidarr/lidarr.component.ts | 81 +++- src/Ombi/ClientApp/styles/base.scss | 21 ++ .../Controllers/External/LidarrController.cs | 21 ++ src/Ombi/wwwroot/translations/en.json | 1 + 26 files changed, 1064 insertions(+), 64 deletions(-) create mode 100644 src/Ombi.Api.Lidarr/Models/ArtistAdd.cs create mode 100644 src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs create mode 100644 src/Ombi.Api.Lidarr/Models/MetadataProfile.cs create mode 100644 src/Ombi.Core/Senders/IMusicSender.cs create mode 100644 src/Ombi.Core/Senders/MusicSender.cs create mode 100644 src/Ombi/ClientApp/app/requests/music/musicrequests.component.html create mode 100644 src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs index d66f9716c..38724f668 100644 --- a/src/Ombi.Api.Lidarr/ILidarrApi.cs +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -16,5 +16,10 @@ namespace Ombi.Api.Lidarr Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl); Task> GetArtists(string apiKey, string baseUrl); Task> GetAllAlbums(string apiKey, string baseUrl); + Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl); + Task MontiorAlbum(int albumId, string apiKey, string baseUrl); + Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl); + Task> GetMetadataProfile(string apiKey, string baseUrl); + Task> GetLanguageProfile(string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index 84e0d090a..572a2b55c 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -63,13 +63,13 @@ namespace Ombi.Api.Lidarr return Api.Request(request); } - public Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl) + public async Task GetArtistByForeignId(string foreignArtistId, string apiKey, string baseUrl) { var request = new Request($"{ApiVersion}/artist/lookup", baseUrl, HttpMethod.Get); request.AddQueryString("term", $"lidarr:{foreignArtistId}"); AddHeaders(request, apiKey); - return Api.Request(request); + return (await Api.Request>(request)).FirstOrDefault(); } public async Task GetAlbumByForeignId(string foreignArtistId, string apiKey, string baseUrl) @@ -105,6 +105,48 @@ namespace Ombi.Api.Lidarr return Api.Request>(request); } + public Task AddArtist(ArtistAdd artist, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/artist", baseUrl, HttpMethod.Post); + request.AddJsonBody(artist); + AddHeaders(request, apiKey); + return Api.Request(request); + } + + public Task MontiorAlbum(int albumId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put); + request.AddJsonBody(new + { + albumIds = new[] { albumId }, + monitored = true + }); + AddHeaders(request, apiKey); + return Api.Request(request); + } + + public Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/album", baseUrl, HttpMethod.Get); + request.AddQueryString("artistId", artistId.ToString()); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetLanguageProfile(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/languageprofile", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + + public Task> GetMetadataProfile(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/metadataprofile", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request>(request); + } + private void AddHeaders(Request request, string key) { request.AddHeader("X-Api-Key", key); diff --git a/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs new file mode 100644 index 000000000..e77a239e6 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs @@ -0,0 +1,48 @@ +using System; +using System.Net.Mime; + +namespace Ombi.Api.Lidarr.Models +{ + public class ArtistAdd + { + public string status { get; set; } + public bool ended { get; set; } + public string artistName { get; set; } + public string foreignArtistId { get; set; } + public int tadbId { get; set; } + public int discogsId { get; set; } + public string overview { get; set; } + public string disambiguation { get; set; } + public Link[] links { get; set; } + public Image[] images { get; set; } + public string remotePoster { get; set; } + public int qualityProfileId { get; set; } + public int languageProfileId { get; set; } + public int metadataProfileId { get; set; } + public bool albumFolder { get; set; } + public bool monitored { get; set; } + public string cleanName { get; set; } + public string sortName { get; set; } + public object[] tags { get; set; } + public DateTime added { get; set; } + public Ratings ratings { get; set; } + public Statistics statistics { get; set; } + public Addoptions addOptions { get; set; } + public string rootFolderPath { get; set; } + } + + public class Addoptions + { + /// + /// Future = 1 + /// Missing = 2 + /// Existing = 3 + /// First = 5 + /// Latest = 4 + /// None = 6 + /// + public int selectedOption { get; set; } + public bool monitored { get; set; } + public bool searchForMissingAlbums { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs b/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs new file mode 100644 index 000000000..f503fe33f --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LanguageProfiles.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class LanguageProfiles + { + public string name { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs new file mode 100644 index 000000000..bda3333f1 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/MetadataProfile.cs @@ -0,0 +1,8 @@ +namespace Ombi.Api.Lidarr.Models +{ + public class MetadataProfile + { + public string name { get; set; } + public int id { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MusicRequestEngine.cs b/src/Ombi.Core/Engine/MusicRequestEngine.cs index bcf9e8c02..a27b17c38 100644 --- a/src/Ombi.Core/Engine/MusicRequestEngine.cs +++ b/src/Ombi.Core/Engine/MusicRequestEngine.cs @@ -15,6 +15,7 @@ using Ombi.Core.Authentication; using Ombi.Core.Engine.Interfaces; using Ombi.Core.Models.UI; using Ombi.Core.Rule.Interfaces; +using Ombi.Core.Senders; using Ombi.Core.Settings; using Ombi.Settings.Settings.Models; using Ombi.Settings.Settings.Models.External; @@ -29,11 +30,11 @@ namespace Ombi.Core.Engine INotificationHelper helper, IRuleEvaluator r, ILogger log, OmbiUserManager manager, IRepository rl, ICacheService cache, ISettingsService ombiSettings, IRepository sub, ILidarrApi lidarr, - ISettingsService lidarrSettings) + ISettingsService lidarrSettings, IMusicSender sender) : base(user, requestService, r, manager, cache, ombiSettings, sub) { NotificationHelper = helper; - //Sender = sender; + _musicSender = sender; Logger = log; _requestLog = rl; _lidarrApi = lidarr; @@ -46,6 +47,7 @@ namespace Ombi.Core.Engine private readonly IRepository _requestLog; private readonly ISettingsService _lidarrSettings; private readonly ILidarrApi _lidarrApi; + private readonly IMusicSender _musicSender; /// /// Requests the Album. @@ -79,7 +81,8 @@ namespace Ombi.Core.Engine RequestedUserId = userDetails.Id, Title = album.title, Disk = album.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url, - Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url + Cover = album.images?.FirstOrDefault(x => x.coverType.Equals("cover"))?.url, + ForeignArtistId = album?.artist?.foreignArtistId ?? string.Empty }; if (requestModel.Cover.IsNullOrEmpty()) { @@ -341,26 +344,25 @@ namespace Ombi.Core.Engine if (request.Approved) { - //TODO - //var result = await Sender.Send(request); - //if (result.Success && result.Sent) - //{ - // return new RequestEngineResult - // { - // Result = true - // }; - //} - - //if (!result.Success) - //{ - // Logger.LogWarning("Tried auto sending movie but failed. Message: {0}", result.Message); - // return new RequestEngineResult - // { - // Message = result.Message, - // ErrorMessage = result.Message, - // Result = false - // }; - //} + var result = await _musicSender.Send(request); + if (result.Success && result.Sent) + { + return new RequestEngineResult + { + Result = true + }; + } + + if (!result.Success) + { + Logger.LogWarning("Tried auto sending album but failed. Message: {0}", result.Message); + return new RequestEngineResult + { + Message = result.Message, + ErrorMessage = result.Message, + Result = false + }; + } // If there are no providers then it's successful but movie has not been sent } diff --git a/src/Ombi.Core/Senders/IMusicSender.cs b/src/Ombi.Core/Senders/IMusicSender.cs new file mode 100644 index 000000000..abeec5c29 --- /dev/null +++ b/src/Ombi.Core/Senders/IMusicSender.cs @@ -0,0 +1,10 @@ +using System.Threading.Tasks; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Core.Senders +{ + public interface IMusicSender + { + Task Send(AlbumRequest model); + } +} \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs new file mode 100644 index 000000000..3baa26003 --- /dev/null +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -0,0 +1,110 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; +using Ombi.Api.Radarr; +using Ombi.Core.Settings; +using Ombi.Helpers; +using Ombi.Settings.Settings.Models.External; +using Ombi.Store.Entities.Requests; +using Serilog; + +namespace Ombi.Core.Senders +{ + public class MusicSender : IMusicSender + { + public MusicSender(ISettingsService lidarr, ILidarrApi lidarrApi) + { + _lidarrSettings = lidarr; + _lidarrApi = lidarrApi; + } + + private readonly ISettingsService _lidarrSettings; + private readonly ILidarrApi _lidarrApi; + + public async Task Send(AlbumRequest model) + { + var settings = await _lidarrSettings.GetSettingsAsync(); + if (settings.Enabled) + { + return await SendToLidarr(model, settings); + } + + return new SenderResult { Success = false, Sent = false, Message = "Lidarr is not enabled" }; + } + + private async Task SendToLidarr(AlbumRequest model, LidarrSettings settings) + { + var qualityToUse = int.Parse(settings.DefaultQualityProfile); + //if (model.QualityOverride > 0) + //{ + // qualityToUse = model.QualityOverride; + //} + + var rootFolderPath = /*model.RootPathOverride <= 0 ?*/ settings.DefaultRootPath /*: await RadarrRootPath(model.RootPathOverride, settings)*/; + + // Need to get the artist + var artist = await _lidarrApi.GetArtistByForeignId(model.ForeignArtistId, settings.ApiKey, settings.FullUri); + + if (artist == null || artist.id <= 0) + { + // Create artist + var newArtist = new ArtistAdd + { + foreignArtistId = model.ForeignArtistId, + addOptions = new Addoptions + { + monitored = true, + searchForMissingAlbums = false, + selectedOption = 6 // None + }, + added = DateTime.Now, + monitored = true, + albumFolder = settings.AlbumFolder, + artistName = model.ArtistName, + cleanName = model.ArtistName.ToLowerInvariant().RemoveSpaces(), + images = new Image[] { }, + languageProfileId = settings.LanguageProfileId, + links = new Link[] {}, + metadataProfileId = settings.MetadataProfileId, + qualityProfileId = qualityToUse, + rootFolderPath = rootFolderPath, + }; + + var result = await _lidarrApi.AddArtist(newArtist, settings.ApiKey, settings.FullUri); + if (result != null && result.id > 0) + { + // Setup the albums + await SetupAlbum(model, result, settings); + } + } + else + { + await SetupAlbum(model, artist, settings); + } + + return new SenderResult { Success = false, Sent = false, Message = "Album is already monitored" }; + } + + private async Task SetupAlbum(AlbumRequest model, ArtistResult artist, LidarrSettings settings) + { + // Get the album id + var albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); + // Get the album we want. + var album = albums.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + if (album == null) + { + return new SenderResult { Message = "Could not find album in Lidarr", Sent = false, Success = false }; + } + + var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri); + if (result.monitored) + { + return new SenderResult {Message = "Album has been requested!", Sent = true, Success = true}; + } + return new SenderResult { Message = "Could not set album to monitored", Sent = false, Success = false }; + } + } +} \ No newline at end of file diff --git a/src/Ombi.DependencyInjection/IocExtensions.cs b/src/Ombi.DependencyInjection/IocExtensions.cs index c4b291936..334b94675 100644 --- a/src/Ombi.DependencyInjection/IocExtensions.cs +++ b/src/Ombi.DependencyInjection/IocExtensions.cs @@ -88,6 +88,7 @@ namespace Ombi.DependencyInjection services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); } diff --git a/src/Ombi.Helpers/StringHelper.cs b/src/Ombi.Helpers/StringHelper.cs index aba120c65..fe26d0751 100644 --- a/src/Ombi.Helpers/StringHelper.cs +++ b/src/Ombi.Helpers/StringHelper.cs @@ -75,5 +75,10 @@ namespace Ombi.Helpers return -1; } + + public static string RemoveSpaces(this string str) + { + return str.Replace(" ", ""); + } } } \ No newline at end of file diff --git a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs index 560a6e2d2..e0bdbdc43 100644 --- a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs @@ -8,5 +8,8 @@ namespace Ombi.Settings.Settings.Models.External public string ApiKey { get; set; } public string DefaultQualityProfile { get; set; } public string DefaultRootPath { get; set; } + public bool AlbumFolder { get; set; } + public int LanguageProfileId { get; set; } + public int MetadataProfileId { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/IRadarr.ts b/src/Ombi/ClientApp/app/interfaces/IRadarr.ts index ebc92507c..b643993f4 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRadarr.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRadarr.ts @@ -8,6 +8,11 @@ export interface IRadarrProfile { id: number; } +export interface IProfiles { + name: string; + id: number; +} + export interface IMinimumAvailability { value: string; name: string; diff --git a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts index 736f60aa2..f7fe3df87 100644 --- a/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts +++ b/src/Ombi/ClientApp/app/interfaces/IRequestModel.ts @@ -23,13 +23,14 @@ export interface IMovieRequests extends IFullBaseRequest { export interface IAlbumRequest extends IBaseRequest { foreignAlbumId: string; foreignArtistId: string; - Disk: string; + disk: string; cover: string; releaseDate: Date; artistName: string; subscribed: boolean; showSubscribe: boolean; + background: any; } export interface IAlbumRequestModel { diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 202dbf2c2..2fb46a2b7 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -90,6 +90,9 @@ export interface ILidarrSettings extends IExternalSettings { defaultQualityProfile: string; defaultRootPath: string; fullRootPath: string; + metadataProfileId: number; + languageProfileId: number; + albumFolder: boolean; } export interface ILandingPageSettings extends ISettings { diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html new file mode 100644 index 000000000..221d829a0 --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -0,0 +1,270 @@ + +
    + + +
    +
    +
    +
    +
    + +
    + poster +
    + + +
    + + +
    +
    + {{ 'Requests.RequestedBy' | translate }} + {{request.requestedUser.userName}} + {{request.requestedUser.alias}} + {{request.requestedUser.userName}} +
    +
    + {{ 'Requests.Status' | translate }} + {{request.status}} +
    + +
    + {{ 'Requests.RequestStatus' | translate }} + + + + + + + + +
    +
    + {{ 'Requests.Denied' | translate }} + + +
    + + +
    {{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
    +
    {{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}
    +
    {{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}
    +
    +
    +
    +
    {{ 'Requests.QualityOverride' | translate }} + {{request.qualityOverrideTitle}} +
    +
    {{ 'Requests.RootFolderOverride' | translate }} + {{request.rootPathOverrideTitle}} +
    +
    + +
    +
    + +
    +
    +
    + + + + + + + + + +
    + +
    +
    +
    + + + +
    + + + + + + +
    + + +
    +
    +
    +
    + + + + +
    + + +
    + + + + + + +

    {{ 'Requests.Filter' | translate }}

    +
    +
    +

    {{ 'Filter.FilterHeaderAvailability' | translate }}

    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    +

    {{ 'Filter.FilterHeaderRequestStatus' | translate }}

    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    + + +
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts new file mode 100644 index 000000000..f004b4019 --- /dev/null +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts @@ -0,0 +1,350 @@ +import { PlatformLocation } from "@angular/common"; +import { Component, Input, OnInit } from "@angular/core"; +import { DomSanitizer } from "@angular/platform-browser"; +import { Subject } from "rxjs"; +import { debounceTime, distinctUntilChanged } from "rxjs/operators"; + +import { AuthService } from "../../auth/auth.service"; +import { FilterType, IAlbumRequest, IFilter, IIssueCategory, IPagenator, OrderType } from "../../interfaces"; +import { NotificationService, RequestService } from "../../services"; + +@Component({ + selector: "music-requests", + templateUrl: "./musicrequests.component.html", +}) +export class MusicRequestsComponent implements OnInit { + public albumRequests: IAlbumRequest[]; + public defaultPoster: string; + + public searchChanged: Subject = new Subject(); + public searchText: string; + + public isAdmin: boolean; // Also PowerUser + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequest: IAlbumRequest; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; + + public filterDisplay: boolean; + public filter: IFilter; + public filterType = FilterType; + + public orderType: OrderType = OrderType.RequestedDateDesc; + public OrderType = OrderType; + + public totalAlbums: number = 100; + private currentlyLoaded: number; + private amountToLoad: number; + + constructor( + private requestService: RequestService, + private auth: AuthService, + private notificationService: NotificationService, + private sanitizer: DomSanitizer, + private readonly platformLocation: PlatformLocation) { + this.searchChanged.pipe( + debounceTime(600), // Wait Xms after the last event before emitting last event + distinctUntilChanged(), // only emit if value is different from previous value + ).subscribe(x => { + this.searchText = x as string; + if (this.searchText === "") { + this.resetSearch(); + return; + } + this.requestService.searchAlbumRequests(this.searchText) + .subscribe(m => { + this.setOverrides(m); + this.albumRequests = m; + }); + }); + this.defaultPoster = "../../../images/default-music-placeholder.png"; + const base = this.platformLocation.getBaseHrefFromDOM(); + if (base) { + this.defaultPoster = "../../.." + base + "/images/default-music-placeholder.png"; + } + } + + public ngOnInit() { + this.amountToLoad = 10; + this.currentlyLoaded = 10; + this.filter = { + availabilityFilter: FilterType.None, + statusFilter: FilterType.None, + }; + this.loadInit(); + this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser"); + } + + public paginate(event: IPagenator) { + const skipAmount = event.first; + this.loadRequests(this.amountToLoad, skipAmount); + } + + public search(text: any) { + this.searchChanged.next(text.target.value); + } + + public removeRequest(request: IAlbumRequest) { + this.requestService.removeAlbumRequest(request); + this.removeRequestFromUi(request); + this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0); + } + + public changeAvailability(request: IAlbumRequest, available: boolean) { + request.available = available; + + if (available) { + this.requestService.markAlbumAvailable({ id: request.id }).subscribe(x => { + if (x.result) { + this.notificationService.success( + `${request.title} Is now available`); + } else { + this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } else { + this.requestService.markAlbumUnavailable({ id: request.id }).subscribe(x => { + if (x.result) { + this.notificationService.success( + `${request.title} Is now unavailable`); + } else { + this.notificationService.warning("Request Available", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } + } + + public approve(request: IAlbumRequest) { + request.approved = true; + this.approveRequest(request); + } + + public deny(request: IAlbumRequest) { + request.denied = true; + this.denyRequest(request); + } + + // public selectRootFolder(searchResult: IAlbumRequest, rootFolderSelected: IRadarrRootFolder, event: any) { + // event.preventDefault(); + // // searchResult.rootPathOverride = rootFolderSelected.id; + // this.setOverride(searchResult); + // this.updateRequest(searchResult); + // } + + // public selectQualityProfile(searchResult: IMovieRequests, profileSelected: IRadarrProfile, event: any) { + // event.preventDefault(); + // searchResult.qualityOverride = profileSelected.id; + // this.setOverride(searchResult); + // this.updateRequest(searchResult); + // } + + public reportIssue(catId: IIssueCategory, req: IAlbumRequest) { + this.issueRequest = req; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.foreignAlbumId; + } + + public ignore(event: any): void { + event.preventDefault(); + } + + public clearFilter(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; + el = el.querySelectorAll("INPUT"); + for (el of el) { + el.checked = false; + el.parentElement.classList.remove("active"); + } + + this.filterDisplay = false; + this.filter.availabilityFilter = FilterType.None; + this.filter.statusFilter = FilterType.None; + + this.resetSearch(); + } + + public filterAvailability(filter: FilterType, el: any) { + this.filterActiveStyle(el); + this.filter.availabilityFilter = filter; + this.loadInit(); + } + + public filterStatus(filter: FilterType, el: any) { + this.filterActiveStyle(el); + this.filter.statusFilter = filter; + this.loadInit(); + } + + public setOrder(value: OrderType, el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + const parent = el.parentElement; + const previousFilter = parent.querySelector(".active"); + + previousFilter.className = ""; + el.className = "active"; + + this.orderType = value; + + this.loadInit(); + } + + // public subscribe(request: IAlbumRequest) { + // request.subscribed = true; + // this.requestService.subscribeToMovie(request.id) + // .subscribe(x => { + // this.notificationService.success("Subscribed To Movie!"); + // }); + // } + + // public unSubscribe(request: IMovieRequests) { + // request.subscribed = false; + // this.requestService.unSubscribeToMovie(request.id) + // .subscribe(x => { + // this.notificationService.success("Unsubscribed Movie!"); + // }); + // } + + private filterActiveStyle(el: any) { + el = el.toElement || el.relatedTarget || el.target || el.srcElement; + + el = el.parentElement; //gets radio div + el = el.parentElement; //gets form group div + el = el.parentElement; //gets status filter div + el = el.querySelectorAll("INPUT"); + for (el of el) { + if (el.checked) { + if (!el.parentElement.classList.contains("active")) { + el.parentElement.className += " active"; + } + } else { + el.parentElement.classList.remove("active"); + } + } + } + + private loadRequests(amountToLoad: number, currentlyLoaded: number) { + this.requestService.getAlbumRequests(amountToLoad, currentlyLoaded, this.orderType, this.filter) + .subscribe(x => { + this.setOverrides(x.collection); + if (!this.albumRequests) { + this.albumRequests = []; + } + this.albumRequests = x.collection; + this.totalAlbums = x.total; + this.currentlyLoaded = currentlyLoaded + amountToLoad; + }); + } + + private approveRequest(request: IAlbumRequest) { + this.requestService.approveAlbum({ id: request.id }) + .subscribe(x => { + request.approved = true; + if (x.result) { + this.notificationService.success( + `Request for ${request.title} has been approved successfully`); + } else { + this.notificationService.warning("Request Approved", x.message ? x.message : x.errorMessage); + request.approved = false; + } + }); + } + + private denyRequest(request: IAlbumRequest) { + this.requestService.denyAlbum({ id: request.id }) + .subscribe(x => { + if (x.result) { + this.notificationService.success( + `Request for ${request.title} has been denied successfully`); + } else { + this.notificationService.warning("Request Denied", x.message ? x.message : x.errorMessage); + request.denied = false; + } + }); + } + + private loadInit() { + this.requestService.getAlbumRequests(this.amountToLoad, 0, this.orderType, this.filter) + .subscribe(x => { + this.albumRequests = x.collection; + this.totalAlbums = x.total; + + this.setOverrides(this.albumRequests); + + if (this.isAdmin) { + // this.radarrService.getQualityProfilesFromSettings().subscribe(c => { + // this.radarrProfiles = c; + // this.albumRequests.forEach((req) => this.setQualityOverrides(req)); + // }); + // this.radarrService.getRootFoldersFromSettings().subscribe(c => { + // this.radarrRootFolders = c; + // this.albumRequests.forEach((req) => this.setRootFolderOverrides(req)); + // }); + } + }); + } + + private resetSearch() { + this.currentlyLoaded = 5; + this.loadInit(); + } + + private removeRequestFromUi(key: IAlbumRequest) { + const index = this.albumRequests.indexOf(key, 0); + if (index > -1) { + this.albumRequests.splice(index, 1); + } + } + + private setOverrides(requests: IAlbumRequest[]): void { + requests.forEach((req) => { + this.setOverride(req); + }); + } + + // private setQualityOverrides(req: IMovieRequests): void { + // if (this.radarrProfiles) { + // const profile = this.radarrProfiles.filter((p) => { + // return p.id === req.qualityOverride; + // }); + // if (profile.length > 0) { + // req.qualityOverrideTitle = profile[0].name; + // } + // } + // } + // private setRootFolderOverrides(req: IMovieRequests): void { + // if (this.radarrRootFolders) { + // const path = this.radarrRootFolders.filter((folder) => { + // return folder.id === req.rootPathOverride; + // }); + // if (path.length > 0) { + // req.rootPathOverrideTitle = path[0].path; + // } + // } + // } + + private setOverride(req: IAlbumRequest): void { + this.setAlbumBackground(req); + // this.setQualityOverrides(req); + // this.setRootFolderOverrides(req); + } + private setAlbumBackground(req: IAlbumRequest) { + if (req.disk === null) { + if(req.cover === null) { + req.disk = this.defaultPoster; + } else { + req.disk = req.cover; + } + } + req.background = this.sanitizer.bypassSecurityTrustStyle + ("url(" + req.cover + ")"); + } +} diff --git a/src/Ombi/ClientApp/app/requests/request.component.html b/src/Ombi/ClientApp/app/requests/request.component.html index 916d3b774..45509fba3 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.html +++ b/src/Ombi/ClientApp/app/requests/request.component.html @@ -9,6 +9,10 @@ {{ 'Requests.TvTab' | translate }} +
  • + {{ 'Requests.MusicTab' | translate }} + +
  • @@ -19,5 +23,8 @@
    +
    + +
    diff --git a/src/Ombi/ClientApp/app/requests/request.component.ts b/src/Ombi/ClientApp/app/requests/request.component.ts index 557bb7a07..d0fa9d0b1 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.ts +++ b/src/Ombi/ClientApp/app/requests/request.component.ts @@ -11,6 +11,7 @@ export class RequestComponent implements OnInit { public showMovie = true; public showTv = false; + public showAlbums = false; public issueCategories: IIssueCategory[]; public issuesEnabled = false; @@ -28,10 +29,18 @@ export class RequestComponent implements OnInit { public selectMovieTab() { this.showMovie = true; this.showTv = false; + this.showAlbums = false; } public selectTvTab() { this.showMovie = false; this.showTv = true; + this.showAlbums = false; + } + + public selectMusicTab() { + this.showMovie = false; + this.showTv = false; + this.showAlbums = true; } } diff --git a/src/Ombi/ClientApp/app/requests/requests.module.ts b/src/Ombi/ClientApp/app/requests/requests.module.ts index 31a45e07a..63d7117f5 100644 --- a/src/Ombi/ClientApp/app/requests/requests.module.ts +++ b/src/Ombi/ClientApp/app/requests/requests.module.ts @@ -8,6 +8,7 @@ import { InfiniteScrollModule } from "ngx-infinite-scroll"; import { ButtonModule, DialogModule, PaginatorModule } from "primeng/primeng"; import { MovieRequestsComponent } from "./movierequests.component"; +import { MusicRequestsComponent } from "./music/musicrequests.component"; // Request import { RequestComponent } from "./request.component"; import { TvRequestChildrenComponent } from "./tvrequest-children.component"; @@ -23,7 +24,6 @@ import { SharedModule } from "../shared/shared.module"; const routes: Routes = [ { path: "", component: RequestComponent, canActivate: [AuthGuard] }, - { path: ":id", component: TvRequestChildrenComponent, canActivate: [AuthGuard] }, ]; @NgModule({ imports: [ @@ -44,6 +44,7 @@ const routes: Routes = [ MovieRequestsComponent, TvRequestsComponent, TvRequestChildrenComponent, + MusicRequestsComponent, ], exports: [ RouterModule, diff --git a/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts index ddd7e48ec..9ef36357a 100644 --- a/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/lidarr.service.ts @@ -3,7 +3,7 @@ import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { Observable } from "rxjs"; -import { ILidarrProfile, ILidarrRootFolder } from "../../interfaces"; +import { ILidarrProfile, ILidarrRootFolder, IProfiles } from "../../interfaces"; import { ILidarrSettings } from "../../interfaces"; import { ServiceHelpers } from "../service.helpers"; @@ -26,4 +26,11 @@ export class LidarrService extends ServiceHelpers { public getQualityProfilesFromSettings(): Observable { return this.http.get(`${this.url}/Profiles/`, {headers: this.headers}); } + + public getMetadataProfiles(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Metadata/`, JSON.stringify(settings), {headers: this.headers}); + } + public getLanguages(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}/Langauges/`,JSON.stringify(settings), {headers: this.headers}); + } } diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 2a9de8b2a..045a73310 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -159,8 +159,8 @@ export class RequestService extends ServiceHelpers { return this.http.post(`${this.url}music/unavailable`, JSON.stringify(Album), {headers: this.headers}); } - public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable> { - return this.http.get>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers}); + public getAlbumRequests(count: number, position: number, order: OrderType, filter: IFilter): Observable> { + return this.http.get>(`${this.url}music/${count}/${position}/${order}/${filter.statusFilter}/${filter.availabilityFilter}`, {headers: this.headers}); } public searchAlbumRequests(search: string): Observable { diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html index d36295038..aa3dbaa66 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html @@ -50,38 +50,59 @@
    -
    -
    - -
    -
    +
    - + +
    A Default Quality Profile is required
    -
    -
    - - -
    - -
    - + +
    A Default Root Path is required
    + +
    + +
    + + + +
    + A Language profile is required +
    + + +
    + +
    + + + + +
    + + A Metadata profile is required +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index 91d7ead80..8100c0194 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from "@angular/core"; import { FormBuilder, FormGroup, Validators } from "@angular/forms"; -import { ILidarrSettings, IMinimumAvailability, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; +import { ILidarrSettings, IMinimumAvailability, IProfiles, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; import { IRadarrSettings } from "../../interfaces"; import { LidarrService, TesterService } from "../../services"; import { NotificationService } from "../../services"; @@ -13,10 +13,14 @@ import { SettingsService } from "../../services"; export class LidarrComponent implements OnInit { public qualities: IRadarrProfile[]; + public languageProfiles: IProfiles[]; + public metadataProfiles: IProfiles[]; public rootFolders: IRadarrRootFolder[]; public minimumAvailabilityOptions: IMinimumAvailability[]; public profilesRunning: boolean; public rootFoldersRunning: boolean; + public metadataRunning: boolean; + public languageRunning: boolean; public advanced = false; public form: FormGroup; @@ -39,6 +43,9 @@ export class LidarrComponent implements OnInit { subDir: [x.subDir], ip: [x.ip, [Validators.required]], port: [x.port, [Validators.required]], + albumFolder: [x.albumFolder], + languageProfileId: [x.languageProfileId, [Validators.required]], + metadataProfileId: [x.metadataProfileId, [Validators.required]], }); if (x.defaultQualityProfile) { @@ -47,35 +54,69 @@ export class LidarrComponent implements OnInit { if (x.defaultRootPath) { this.getRootFolders(this.form); } + if (x.languageProfileId) { + this.getLanguageProfiles(this.form); + } + if (x.metadataProfileId) { + this.getMetadataProfiles(this.form); + } }); this.qualities = []; this.qualities.push({ name: "Please Select", id: -1 }); - + this.rootFolders = []; this.rootFolders.push({ path: "Please Select", id: -1 }); + + this.languageProfiles = []; + this.languageProfiles.push({ name: "Please Select", id: -1 }); + + this.metadataProfiles = []; + this.metadataProfiles.push({ name: "Please Select", id: -1 }); } public getProfiles(form: FormGroup) { - this.profilesRunning = true; - this.lidarrService.getQualityProfiles(form.value).subscribe(x => { - this.qualities = x; - this.qualities.unshift({ name: "Please Select", id: -1 }); - - this.profilesRunning = false; - this.notificationService.success("Successfully retrieved the Quality Profiles"); - }); + this.profilesRunning = true; + this.lidarrService.getQualityProfiles(form.value).subscribe(x => { + this.qualities = x; + this.qualities.unshift({ name: "Please Select", id: -1 }); + + this.profilesRunning = false; + this.notificationService.success("Successfully retrieved the Quality Profiles"); + }); } public getRootFolders(form: FormGroup) { - this.rootFoldersRunning = true; - this.lidarrService.getRootFolders(form.value).subscribe(x => { - this.rootFolders = x; - this.rootFolders.unshift({ path: "Please Select", id: -1 }); - - this.rootFoldersRunning = false; - this.notificationService.success("Successfully retrieved the Root Folders"); - }); + this.rootFoldersRunning = true; + this.lidarrService.getRootFolders(form.value).subscribe(x => { + this.rootFolders = x; + this.rootFolders.unshift({ path: "Please Select", id: -1 }); + + this.rootFoldersRunning = false; + this.notificationService.success("Successfully retrieved the Root Folders"); + }); + } + + public getMetadataProfiles(form: FormGroup) { + this.metadataRunning = true; + this.lidarrService.getMetadataProfiles(form.value).subscribe(x => { + this.metadataProfiles = x; + this.metadataProfiles.unshift({ name: "Please Select", id: -1 }); + + this.metadataRunning = false; + this.notificationService.success("Successfully retrieved the Metadata profiles"); + }); + } + + public getLanguageProfiles(form: FormGroup) { + this.languageRunning = true; + this.lidarrService.getLanguages(form.value).subscribe(x => { + this.languageProfiles = x; + this.languageProfiles.unshift({ name: "Please Select", id: -1 }); + + this.languageRunning = false; + this.notificationService.success("Successfully retrieved the Language profiles"); + }); } public test(form: FormGroup) { @@ -93,12 +134,12 @@ export class LidarrComponent implements OnInit { }); } -public onSubmit(form: FormGroup) { + public onSubmit(form: FormGroup) { if (form.invalid) { this.notificationService.error("Please check your entered values"); return; } - if(form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { + if (form.controls.defaultQualityProfile.value === "-1" || form.controls.defaultRootPath.value === "Please Select") { this.notificationService.error("Please check your entered values"); return; } diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index 593511316..1afad391c 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -1003,4 +1003,25 @@ a > h4:hover { white-space: normal; vertical-align: baseline; border-radius: .25em; +} + +.form-control-grid { + display: block; + width: inherit !important; + height: 39px; + color: #fefefe; + border-radius: 5px; + padding: 8px 16px; + font-size: 15px; + line-height: 1.42857143; + color: #2b3e50; + background-color: #ffffff; + background-image: none; + border: 1px solid transparent; + border-radius: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); + -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; } \ No newline at end of file diff --git a/src/Ombi/Controllers/External/LidarrController.cs b/src/Ombi/Controllers/External/LidarrController.cs index 084d4f258..076d68241 100644 --- a/src/Ombi/Controllers/External/LidarrController.cs +++ b/src/Ombi/Controllers/External/LidarrController.cs @@ -50,6 +50,27 @@ namespace Ombi.Controllers.External return await _lidarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); } + /// + /// Gets the Lidarr metadata profiles. + /// + /// The settings. + /// + [HttpPost("Metadata")] + public async Task> GetMetadataProfiles([FromBody] LidarrSettings settings) + { + return await _lidarrApi.GetMetadataProfile(settings.ApiKey, settings.FullUri); + } + /// + /// Gets the Lidarr Langauge profiles. + /// + /// The settings. + /// + [HttpPost("Langauges")] + public async Task> GetLanguageProfiles([FromBody] LidarrSettings settings) + { + return await _lidarrApi.GetLanguageProfile(settings.ApiKey, settings.FullUri); + } + /// /// Gets the Lidarr profiles using the saved settings /// The data is cached for an hour diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 4fd3ccb1b..74a6fc7cc 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -119,6 +119,7 @@ "Below you can see yours and all other requests, as well as their download and approval status.", "MoviesTab": "Movies", "TvTab": "TV Shows", + "MusicTab":"Music", "RequestedBy": "Requested By:", "Status": "Status:", "RequestStatus": "Request status:", From 0b7a7a6bbbe86e326dc00a1a21c081cc40590219 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 26 Aug 2018 23:26:15 +0100 Subject: [PATCH 364/495] Sorted out the requests page UI !wip #2313 --- src/Ombi.Core/Engine/MusicSearchEngine.cs | 2 +- .../music/musicrequests.component.html | 21 +++++++--------- .../search/music/albumsearch.component.scss | 3 --- .../app/search/music/albumsearch.component.ts | 1 - src/Ombi/ClientApp/styles/base.scss | 24 ++++--------------- src/Ombi/wwwroot/translations/en.json | 1 + 6 files changed, 14 insertions(+), 38 deletions(-) delete mode 100644 src/Ombi/ClientApp/app/search/music/albumsearch.component.scss diff --git a/src/Ombi.Core/Engine/MusicSearchEngine.cs b/src/Ombi.Core/Engine/MusicSearchEngine.cs index 17f1aed54..d0e577801 100644 --- a/src/Ombi.Core/Engine/MusicSearchEngine.cs +++ b/src/Ombi.Core/Engine/MusicSearchEngine.cs @@ -154,7 +154,7 @@ namespace Ombi.Core.Engine Title = a.title, Disk = a.images?.FirstOrDefault(x => x.coverType.Equals("disc"))?.url }; - if (vm.Monitored) + if (a.artistId > 0) { //TODO THEY HAVE FIXED THIS IN DEV // The JSON is different for some stupid reason diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html index 221d829a0..85150edeb 100644 --- a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -44,7 +44,7 @@
    -
    +
    @@ -77,10 +77,6 @@ {{request.requestedUser.alias}} {{request.requestedUser.userName}}
    -
    - {{ 'Requests.Status' | translate }} - {{request.status}} -
    {{ 'Requests.RequestStatus' | translate }} @@ -101,19 +97,18 @@
    -
    {{ 'Requests.TheatricalRelease' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
    -
    {{ 'Requests.DigitalRelease' | translate: {date: request.digitalReleaseDate | date: 'mediumDate'} }}
    -
    {{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date}}
    +
    {{ 'Requests.ReleaseDate' | translate: {date: request.releaseDate | date: 'mediumDate'} }}
    +
    {{ 'Requests.RequestDate' | translate }} {{request.requestedDate | date: 'mediumDate'}}

    -
    +
    @@ -130,7 +125,7 @@
    -->
    -
    + @@ -168,13 +163,13 @@
    --> -
    +
    - + diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.scss b/src/Ombi/ClientApp/app/search/music/albumsearch.component.scss deleted file mode 100644 index 799b4957f..000000000 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.album-cover { - width:300px; -} diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts index 29824a970..0f9a373e2 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts @@ -9,7 +9,6 @@ import { NotificationService, RequestService } from "../../services"; @Component({ selector: "album-search", templateUrl: "./albumsearch.component.html", - styleUrls: ["./albumsearch.component.scss"], }) export class AlbumSearchComponent { diff --git a/src/Ombi/ClientApp/styles/base.scss b/src/Ombi/ClientApp/styles/base.scss index 1afad391c..a9f3119fa 100644 --- a/src/Ombi/ClientApp/styles/base.scss +++ b/src/Ombi/ClientApp/styles/base.scss @@ -1005,23 +1005,7 @@ a > h4:hover { border-radius: .25em; } -.form-control-grid { - display: block; - width: inherit !important; - height: 39px; - color: #fefefe; - border-radius: 5px; - padding: 8px 16px; - font-size: 15px; - line-height: 1.42857143; - color: #2b3e50; - background-color: #ffffff; - background-image: none; - border: 1px solid transparent; - border-radius: 0; - -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); - box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); - -webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s; - -o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; - transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s; -} \ No newline at end of file +.album-cover { + width:300px; +} + diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index 74a6fc7cc..d5d8969df 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -125,6 +125,7 @@ "RequestStatus": "Request status:", "Denied": " Denied:", "TheatricalRelease": "Theatrical Release: {{date}}", + "ReleaseDate": "Released: {{date}}", "TheatricalReleaseSort": "Theatrical Release", "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Request Date:", From e02c8e4014aba9c95b822c0c4bae16a29fefa362 Mon Sep 17 00:00:00 2001 From: Jamie Date: Sun, 26 Aug 2018 23:42:53 +0100 Subject: [PATCH 365/495] It works now when we request an album when we do not have the artist in Lidarr. Waiting on https://github.com/lidarr/Lidarr/issues/459 to do when we have the artist --- src/Ombi.Api.Lidarr/LidarrApi.cs | 4 ++-- src/Ombi.Api.Lidarr/Models/ArtistAdd.cs | 1 + src/Ombi.Core/Senders/MusicSender.cs | 22 ++++++++++++++++--- .../requests/music/musicrequests.component.ts | 7 +++--- .../ClientApp/app/services/request.service.ts | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index 572a2b55c..cf358699c 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -113,7 +113,7 @@ namespace Ombi.Api.Lidarr return Api.Request(request); } - public Task MontiorAlbum(int albumId, string apiKey, string baseUrl) + public async Task MontiorAlbum(int albumId, string apiKey, string baseUrl) { var request = new Request($"{ApiVersion}/album/monitor", baseUrl, HttpMethod.Put); request.AddJsonBody(new @@ -122,7 +122,7 @@ namespace Ombi.Api.Lidarr monitored = true }); AddHeaders(request, apiKey); - return Api.Request(request); + return (await Api.Request>(request)).FirstOrDefault(); } public Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl) diff --git a/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs index e77a239e6..65aec3ac8 100644 --- a/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs +++ b/src/Ombi.Api.Lidarr/Models/ArtistAdd.cs @@ -44,5 +44,6 @@ namespace Ombi.Api.Lidarr.Models public int selectedOption { get; set; } public bool monitored { get; set; } public bool searchForMissingAlbums { get; set; } + public string[] AlbumsToMonitor { get; set; } // Uses the MusicBrainzAlbumId! } } \ No newline at end of file diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs index 3baa26003..5e1e44126 100644 --- a/src/Ombi.Core/Senders/MusicSender.cs +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -57,7 +57,8 @@ namespace Ombi.Core.Senders { monitored = true, searchForMissingAlbums = false, - selectedOption = 6 // None + selectedOption = 6, // None + AlbumsToMonitor = new[] {model.ForeignAlbumId} }, added = DateTime.Now, monitored = true, @@ -76,7 +77,7 @@ namespace Ombi.Core.Senders if (result != null && result.id > 0) { // Setup the albums - await SetupAlbum(model, result, settings); + return new SenderResult { Message = "Album has been requested!", Sent = true, Success = true }; } } else @@ -91,9 +92,24 @@ namespace Ombi.Core.Senders { // Get the album id var albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); - // Get the album we want. var album = albums.FirstOrDefault(x => x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + var maxRetryCount = 10; // 5 seconds + var currentRetry = 0; + while (!albums.Any() || album == null) + { + if (currentRetry >= maxRetryCount) + { + break; + } + currentRetry++; + await Task.Delay(500); + albums = await _lidarrApi.GetAllAlbumsByArtistId(artist.id, settings.ApiKey, settings.FullUri); + album = albums.FirstOrDefault(x => + x.foreignAlbumId.Equals(model.ForeignAlbumId, StringComparison.InvariantCultureIgnoreCase)); + } + // Get the album we want. + if (album == null) { return new SenderResult { Message = "Could not find album in Lidarr", Sent = false, Success = false }; diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts index f004b4019..8f8096acd 100644 --- a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.ts @@ -88,9 +88,10 @@ export class MusicRequestsComponent implements OnInit { } public removeRequest(request: IAlbumRequest) { - this.requestService.removeAlbumRequest(request); - this.removeRequestFromUi(request); - this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0); + this.requestService.removeAlbumRequest(request).subscribe(x => { + this.removeRequestFromUi(request); + this.loadRequests(this.amountToLoad, this.currentlyLoaded = 0); + }); } public changeAvailability(request: IAlbumRequest, available: boolean) { diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 045a73310..039939a84 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -167,7 +167,7 @@ export class RequestService extends ServiceHelpers { return this.http.get(`${this.url}music/search/${search}`, {headers: this.headers}); } - public removeAlbumRequest(request: IAlbumRequest) { + public removeAlbumRequest(request: IAlbumRequest): any { this.http.delete(`${this.url}music/${request.id}`, {headers: this.headers}).subscribe(); } From fd8bfeb1e44ff6546d6066c594afece06ca98b33 Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 27 Aug 2018 00:04:57 +0100 Subject: [PATCH 366/495] fixed linting !wip --- .../ClientApp/app/requests/music/musicrequests.component.html | 2 +- src/Ombi/ClientApp/app/search/music/albumsearch.component.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html index 85150edeb..43819ad76 100644 --- a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -212,7 +212,7 @@
    - +
    diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html index 6b5cc52a9..b23a73221 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -75,7 +75,7 @@ -->
    -
    +
    From 91a04f6ca707700c955a9513a04696ba3fd2ad1f Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 12:30:53 +0100 Subject: [PATCH 367/495] Add vscode to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 771a7bacd..587f09568 100644 --- a/.gitignore +++ b/.gitignore @@ -243,3 +243,6 @@ _Pvt_Extensions # CAKE - C# Make /Tools/* *.db-journal + +# Ignore local vscode config +*.vscode From 71009f4942bb5451b5aaf024fd6d5d537e94e829 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 15:05:49 +0100 Subject: [PATCH 368/495] Fix query for fetching requested tv shows --- src/Ombi.Core/Engine/TvRequestEngine.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 939b0fe3e..2d937a377 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -631,7 +631,10 @@ namespace Ombi.Core.Engine }; } - IQueryable log = _requestLog.GetAll().Where(x => x.UserId == user.Id && x.RequestType == RequestType.TvShow); + IQueryable log = _requestLog.GetAll() + .Where(x => x.UserId == user.Id + && x.RequestType == RequestType.TvShow + && x.RequestDate >= DateTime.UtcNow.AddDays(-7)); // Needed, due to a bug which would cause all episode counts to be 0 int zeroEpisodeCount = await log.Where(x => x.EpisodeCount == 0).Select(x => x.EpisodeCount).CountAsync(); @@ -640,8 +643,7 @@ namespace Ombi.Core.Engine int count = limit - (zeroEpisodeCount + episodeCount); - DateTime oldestRequestedAt = await log.Where(x => x.RequestDate >= DateTime.UtcNow.AddDays(-7)) - .OrderBy(x => x.RequestDate) + DateTime oldestRequestedAt = await log.OrderBy(x => x.RequestDate) .Select(x => x.RequestDate) .FirstOrDefaultAsync(); From c12cb10972276d521944dd287e6781b09d0352bf Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 15:40:00 +0100 Subject: [PATCH 369/495] Add text to translation file --- .../app/requests/remainingrequests.component.html | 8 ++++---- src/Ombi/wwwroot/translations/en.json | 11 ++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.html b/src/Ombi/ClientApp/app/requests/remainingrequests.component.html index 769cd79c2..565de7473 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.html @@ -1,15 +1,15 @@ 

    - {{remaining.remaining}}/{{remaining.limit}} requests remaining + {{'Requests.Remaining.Quota' | translate: {remaining: remaining.remaining, total: remaining.limit} }}

    - Another request will be added in {{daysUntil}} {{daysUntil == 1 ? "day" : "days"}} + {{daysUntil == 1 ? 'Requests.Remaining.NextDay' : "Requests.Remaining.NextDays" | translate: {time: daysUntil} }}

    - Another request will be added in {{hoursUntil}} {{hoursUntil == 1 ? "hour" : "hours"}} + {{hoursUntil == 1 ? 'Requests.Remaining.NextHour' : "Requests.Remaining.NextHours" | translate: {time: hoursUntil} }}

    - Another request will be added in {{minutesUntil}} {{minutesUntil == 1 ? "minute" : "minutes"}} + {{minutesUntil == 1 ? 'Requests.Remaining.NextMinute' : "Requests.Remaining.NextMinutes" | translate: {time: minutesUntil} }}

    diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index b70e065cb..a2c7f20dc 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -145,7 +145,16 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc":"Status ▲", - "SortStatusDesc":"Status ▼" + "SortStatusDesc":"Status ▼", + "Remaining": { + "Quota": "{{remaining}}/{{total}} requests remaining", + "NextDays": "Another request will be added in {{time}} days", + "NextDay": "Another request will be added in {{time}} day", + "NextHours": "Another request will be added in {{time} hours", + "NextHour": "Another request will be added in {{time} hour", + "NextMinutes": "Another request will be added in {{time}} minutes", + "NextMinute": "Another request will be added in {{time}} minute" + } }, "Issues":{ "Title":"Issues", From 967109f4b2623dfd843d0d84c66c313ea2a03c5b Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 16:04:46 +0100 Subject: [PATCH 370/495] Refactor code --- .../app/requests/remainingrequests.component.html | 8 ++++---- .../ClientApp/app/requests/remainingrequests.component.ts | 4 ---- src/Ombi/wwwroot/translations/en.json | 4 +--- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.html b/src/Ombi/ClientApp/app/requests/remainingrequests.component.html index 565de7473..11cd28b34 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.html @@ -3,13 +3,13 @@ {{'Requests.Remaining.Quota' | translate: {remaining: remaining.remaining, total: remaining.limit} }}

    - {{daysUntil == 1 ? 'Requests.Remaining.NextDay' : "Requests.Remaining.NextDays" | translate: {time: daysUntil} }} + {{'Requests.Remaining.NextDays' | translate: {time: daysUntil} }}

    - {{hoursUntil == 1 ? 'Requests.Remaining.NextHour' : "Requests.Remaining.NextHours" | translate: {time: hoursUntil} }} + {{'Requests.Remaining.NextHours' | translate: {time: hoursUntil} }}

    -

    - {{minutesUntil == 1 ? 'Requests.Remaining.NextMinute' : "Requests.Remaining.NextMinutes" | translate: {time: minutesUntil} }} +

    + {{(minutesUntil == 1 ? 'Requests.Remaining.NextMinute' : 'Requests.Remaining.NextMinutes') | translate: {time: minutesUntil} }}

    diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts index 4f4439c13..980256d69 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -27,10 +27,6 @@ export class RemainingRequestsComponent implements OnInit { this.update(); }); - setInterval(() => { - self.calculateTime(); - }, 10000); - setInterval(() => { self.update(); }, 60000); diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index a2c7f20dc..46be04a6b 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -149,9 +149,7 @@ "Remaining": { "Quota": "{{remaining}}/{{total}} requests remaining", "NextDays": "Another request will be added in {{time}} days", - "NextDay": "Another request will be added in {{time}} day", - "NextHours": "Another request will be added in {{time} hours", - "NextHour": "Another request will be added in {{time} hour", + "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" } From 93e9b4cd4ccacdb2025dbe04b4cc8d54e06addb4 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 16:08:06 +0100 Subject: [PATCH 371/495] Remove unused module --- src/Ombi/ClientApp/app/requests/remainingrequests.module.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts index acbbed256..70a12bf16 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts @@ -8,15 +8,12 @@ import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { SidebarModule, TooltipModule, TreeTableModule } from "primeng/primeng"; import { RequestService } from "../services"; -import { SharedModule } from "../shared/shared.module"; - @NgModule({ imports: [ CommonModule, FormsModule, NgbModule.forRoot(), TreeTableModule, - SharedModule, SidebarModule, TooltipModule, ], From e6f24eabb45eb38a9e53a4059507addddfc9a458 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 16:52:09 +0100 Subject: [PATCH 372/495] Change way remainingrequests component is notified --- .../requests/remainingrequests.component.ts | 2 +- .../app/search/moviesearch.component.ts | 1 + .../app/search/seriesinformation.component.ts | 1 + .../app/search/tvsearch.component.ts | 1 + .../ClientApp/app/services/request.service.ts | 25 +++---------------- 5 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts index 980256d69..8a13d2cfb 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -23,7 +23,7 @@ export class RemainingRequestsComponent implements OnInit { this.update(); - this.requestService.onRequested().subscribe(m => { + this.requestService.requestEvents.subscribe(() => { this.update(); }); diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 965837bae..fece0cc74 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -88,6 +88,7 @@ export class MovieSearchComponent implements OnInit { try { this.requestService.requestMovie({ theMovieDbId: searchResult.id }) .subscribe(x => { + this.requestService.requestEvents.next(); this.result = x; if (this.result.result) { this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts index 5c0088268..91234780d 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts @@ -62,6 +62,7 @@ export class SeriesInformationComponent implements OnInit { this.requestService.requestTv(viewModel) .subscribe(x => { + this.requestService.requestEvents.next(); this.result = x as IRequestEngineResult; if (this.result.result) { this.notificationService.success( diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index a41f34586..e08b84567 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -161,6 +161,7 @@ export class TvSearchComponent implements OnInit { this.requestService.requestTv(viewModel) .subscribe(x => { + this.requestService.requestEvents.next(); this.result = x; if (this.result.result) { this.notificationService.success( diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index a7fbc76bf..a8032d5a9 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -13,15 +13,12 @@ import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Injectable() export class RequestService extends ServiceHelpers { - private requestEvents = new ReplaySubject(); + public readonly requestEvents = new ReplaySubject(); + constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/Request/", platformLocation); } - public onRequested(): Observable { - return this.requestEvents.asObservable(); - } - public getRemainingMovieRequests(): Observable { return this.http.get(`${this.url}movie/remaining`, {headers: this.headers}); } @@ -31,14 +28,7 @@ export class RequestService extends ServiceHelpers { } public requestMovie(movie: IMovieRequestModel): Observable { - const observer = Observable.create(observer => { - this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}).subscribe(m => { - observer.next(m); - this.requestEvents.next(m); - }); - }); - - return observer; + return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); } public getTotalMovies(): Observable { @@ -50,14 +40,7 @@ export class RequestService extends ServiceHelpers { } public requestTv(tv: ITvRequestViewModel): Observable { - const observer = Observable.create(observer => { - return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }).subscribe(m => { - observer.next(m); - this.requestEvents.next(m); - }); - }); - - return observer; + return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }); } public approveMovie(movie: IMovieUpdateModel): Observable { From 26904d3947a544d320511011a87c6192931df872 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 16:55:50 +0100 Subject: [PATCH 373/495] Remove import --- src/Ombi/ClientApp/app/requests/remainingrequests.module.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts index 70a12bf16..411a94dfd 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.module.ts @@ -1,5 +1,4 @@ -import { CommonModule } from "@angular/common"; -import { NgModule } from "@angular/core"; +import { NgModule } from "@angular/core"; import { FormsModule } from "@angular/forms"; import { RouterModule } from "@angular/router"; @@ -10,7 +9,6 @@ import { RequestService } from "../services"; @NgModule({ imports: [ - CommonModule, FormsModule, NgbModule.forRoot(), TreeTableModule, From 4cad24f8e95b152358fe67619b2efa62678a8328 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 23:51:44 +0100 Subject: [PATCH 374/495] Move logic for notifying when reuqest is complete --- .../ClientApp/app/requests/remainingrequests.component.ts | 4 +++- src/Ombi/ClientApp/app/search/moviesearch.component.html | 2 +- src/Ombi/ClientApp/app/search/moviesearch.component.ts | 3 ++- .../ClientApp/app/search/seriesinformation.component.ts | 6 ++++-- src/Ombi/ClientApp/app/search/tvsearch.component.html | 4 ++-- src/Ombi/ClientApp/app/search/tvsearch.component.ts | 3 ++- src/Ombi/ClientApp/app/services/request.service.ts | 4 +--- 7 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts index 8a13d2cfb..978480e94 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -2,6 +2,7 @@ import { RequestService } from "../services"; import { Component, Input, OnInit } from "@angular/core"; +import { Observable } from "rxjs"; @Component({ selector: "remaining-requests", @@ -14,6 +15,7 @@ export class RemainingRequestsComponent implements OnInit { public daysUntil: number; public hoursUntil: number; public minutesUntil: number; + @Input() quotaRefreshEvents: Observable; constructor(private requestService: RequestService) { } @@ -23,7 +25,7 @@ export class RemainingRequestsComponent implements OnInit { this.update(); - this.requestService.requestEvents.subscribe(() => { + this.quotaRefreshEvents.subscribe(() => { this.update(); }); diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.html b/src/Ombi/ClientApp/app/search/moviesearch.component.html index 6d8c040ac..eb3cb1b87 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.html +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.html @@ -20,7 +20,7 @@
    - +
    diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index fece0cc74..1519941ec 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -17,6 +17,7 @@ export class MovieSearchComponent implements OnInit { public searchText: string; public searchChanged: Subject = new Subject(); + public movieRequested: Subject = new Subject(); public movieResults: ISearchMovieResult[]; public result: IRequestEngineResult; @@ -88,7 +89,7 @@ export class MovieSearchComponent implements OnInit { try { this.requestService.requestMovie({ theMovieDbId: searchResult.id }) .subscribe(x => { - this.requestService.requestEvents.next(); + this.movieRequested.next(); this.result = x; if (this.result.result) { this.translate.get("Search.RequestAdded", { title: searchResult.title }).subscribe(x => { diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts index 91234780d..979afc144 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts @@ -7,6 +7,7 @@ import { SearchService } from "../services"; import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; import { IEpisodesRequests } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; +import { Subject } from "rxjs"; @Component({ selector: "seriesinformation", @@ -18,9 +19,10 @@ export class SeriesInformationComponent implements OnInit { public result: IRequestEngineResult; public series: ISearchTvResult; public requestedEpisodes: IEpisodesRequests[] = []; - @Input() private seriesId: number; + @Input() public tvRequested: Subject; + constructor(private searchService: SearchService, private requestService: RequestService, private notificationService: NotificationService) { } public ngOnInit() { @@ -62,7 +64,7 @@ export class SeriesInformationComponent implements OnInit { this.requestService.requestTv(viewModel) .subscribe(x => { - this.requestService.requestEvents.next(); + this.tvRequested.next(); this.result = x as IRequestEngineResult; if (this.result.result) { this.notificationService.success( diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.html b/src/Ombi/ClientApp/app/search/tvsearch.component.html index 5f4938cf1..127077b3b 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.html +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.html @@ -27,7 +27,7 @@
    - +
    @@ -153,7 +153,7 @@
    - +

    diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index e08b84567..632900091 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -18,6 +18,7 @@ export class TvSearchComponent implements OnInit { public searchText: string; public searchChanged = new Subject(); public tvResults: ISearchTvResult[]; + public tvRequested: Subject = new Subject(); public result: IRequestEngineResult; public searchApplied = false; public defaultPoster: string; @@ -161,7 +162,7 @@ export class TvSearchComponent implements OnInit { this.requestService.requestTv(viewModel) .subscribe(x => { - this.requestService.requestEvents.next(); + this.tvRequested.next(); this.result = x; if (this.result.result) { this.notificationService.success( diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index a8032d5a9..1ebed5dcd 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -2,7 +2,7 @@ import { PlatformLocation } from "@angular/common"; import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; -import { Observable, ReplaySubject } from "rxjs"; +import { Observable } from "rxjs"; import { TreeNode } from "primeng/primeng"; import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests, IMovieUpdateModel, IRequestEngineResult, IRequestsViewModel, ITvRequests, ITvUpdateModel, OrderType } from "../interfaces"; @@ -13,8 +13,6 @@ import { IRemainingRequests } from "../interfaces/IRemainingRequests"; @Injectable() export class RequestService extends ServiceHelpers { - public readonly requestEvents = new ReplaySubject(); - constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/Request/", platformLocation); } From dcae229a8321fe386d9e0437dfbd802df4a9b98e Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Mon, 27 Aug 2018 23:58:05 +0100 Subject: [PATCH 375/495] Fix lint errors --- .../ClientApp/app/requests/remainingrequests.component.ts | 2 +- src/Ombi/ClientApp/app/search/seriesinformation.component.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts index 978480e94..4de649f6a 100644 --- a/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/remainingrequests.component.ts @@ -15,7 +15,7 @@ export class RemainingRequestsComponent implements OnInit { public daysUntil: number; public hoursUntil: number; public minutesUntil: number; - @Input() quotaRefreshEvents: Observable; + @Input() public quotaRefreshEvents: Observable; constructor(private requestService: RequestService) { } diff --git a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts index 979afc144..6a918a69a 100644 --- a/src/Ombi/ClientApp/app/search/seriesinformation.component.ts +++ b/src/Ombi/ClientApp/app/search/seriesinformation.component.ts @@ -7,6 +7,7 @@ import { SearchService } from "../services"; import { INewSeasonRequests, IRequestEngineResult, ISeasonsViewModel, ITvRequestViewModel } from "../interfaces"; import { IEpisodesRequests } from "../interfaces"; import { ISearchTvResult } from "../interfaces"; + import { Subject } from "rxjs"; @Component({ @@ -19,9 +20,8 @@ export class SeriesInformationComponent implements OnInit { public result: IRequestEngineResult; public series: ISearchTvResult; public requestedEpisodes: IEpisodesRequests[] = []; - @Input() private seriesId: number; - @Input() public tvRequested: Subject; + @Input() private seriesId: number; constructor(private searchService: SearchService, private requestService: RequestService, private notificationService: NotificationService) { } From 82c353a727f02d2b80fdb651b75d2be4b17e83d0 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 28 Aug 2018 09:29:42 +0100 Subject: [PATCH 376/495] !wip added music into the newsletter and also added issue reporting to the albums --- .../Jobs/Ombi/HtmlTemplateGenerator.cs | 26 ++-- src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs | 113 +++++++++++++++++- .../Notifications/NewsletterSettings.cs | 1 + src/Ombi.Store/Entities/LidarrAlbumCache.cs | 1 + src/Ombi.Store/Entities/RecentlyAddedLog.cs | 7 +- .../music/musicrequests.component.html | 4 +- .../search/music/albumsearch.component.html | 21 ++-- .../app/search/music/albumsearch.component.ts | 20 +++- 8 files changed, 165 insertions(+), 28 deletions(-) diff --git a/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs index 51e920b15..09b7d9858 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs @@ -1,4 +1,5 @@ using System.Text; +using Ombi.Helpers; namespace Ombi.Schedule.Jobs.Ombi { @@ -22,13 +23,20 @@ namespace Ombi.Schedule.Jobs.Ombi protected virtual void AddMediaServerUrl(StringBuilder sb, string mediaurl, string url) { - sb.Append("
    "); - sb.Append(""); - sb.Append(""); + if (url.HasValue()) + { + sb.Append(""); + sb.Append( + ""); + sb.Append(""); + } + sb.Append("
    "); - sb.AppendFormat("", mediaurl); - sb.AppendFormat("", url); - sb.Append(""); - sb.Append("
    "); + sb.AppendFormat("", mediaurl); + sb.AppendFormat( + "", + url); + sb.Append(""); + sb.Append("
    "); sb.Append(""); } @@ -44,9 +52,9 @@ namespace Ombi.Schedule.Jobs.Ombi { sb.Append(""); sb.Append(""); - sb.AppendFormat("", url); + if(url.HasValue()) sb.AppendFormat("", url); sb.AppendFormat("

    {0}

    ", title); - sb.Append("
    "); + if (url.HasValue()) sb.Append(""); sb.Append(""); sb.Append(""); } diff --git a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs index 6e89d167e..f152f6b4b 100644 --- a/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs +++ b/src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs @@ -9,6 +9,8 @@ using MailKit; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using Ombi.Api.Lidarr; +using Ombi.Api.Lidarr.Models; using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TvMaze; @@ -18,6 +20,7 @@ using Ombi.Notifications; using Ombi.Notifications.Models; using Ombi.Notifications.Templates; using Ombi.Settings.Settings.Models; +using Ombi.Settings.Settings.Models.External; using Ombi.Settings.Settings.Models.Notifications; using Ombi.Store.Entities; using Ombi.Store.Repository; @@ -29,7 +32,8 @@ namespace Ombi.Schedule.Jobs.Ombi public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository addedLog, IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService custom, ISettingsService emailSettings, INotificationTemplatesRepository templateRepo, - UserManager um, ISettingsService newsletter, ILogger log) + UserManager um, ISettingsService newsletter, ILogger log, + ILidarrApi lidarrApi, IRepository albumCache, ISettingsService lidarrSettings) { _plex = plex; _emby = emby; @@ -46,6 +50,10 @@ namespace Ombi.Schedule.Jobs.Ombi _customizationSettings.ClearCache(); _newsletterSettings.ClearCache(); _log = log; + _lidarrApi = lidarrApi; + _lidarrAlbumRepository = albumCache; + _lidarrSettings = lidarrSettings; + _lidarrSettings.ClearCache(); } private readonly IPlexContentRepository _plex; @@ -60,6 +68,9 @@ namespace Ombi.Schedule.Jobs.Ombi private readonly ISettingsService _newsletterSettings; private readonly UserManager _userManager; private readonly ILogger _log; + private readonly ILidarrApi _lidarrApi; + private readonly IRepository _lidarrAlbumRepository; + private readonly ISettingsService _lidarrSettings; public async Task Start(NewsletterSettings settings, bool test) { @@ -87,21 +98,26 @@ namespace Ombi.Schedule.Jobs.Ombi // Get the Content var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); + var lidarrContent = _lidarrAlbumRepository.GetAll().AsNoTracking(); var addedLog = _recentlyAddedLog.GetAll(); var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId); + var addedAlbumLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Lidarr && x.ContentType == ContentType.Album).Select(x => x.AlbumId); var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode); var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode); + // Filter out the ones that we haven't sent yet var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && x.HasTheMovieDb && !addedPlexMovieLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && x.HasTheMovieDb && !addedEmbyMoviesLogIds.Contains(StringHelper.IntParseLinq(x.TheMovieDbId))); + var lidarrContentAlbumsToSend = lidarrContent.Where(x => !addedAlbumLogIds.Contains(x.ForeignAlbumId)).ToHashSet(); _log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count()); _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count()); + _log.LogInformation("Albums to send: {0}", lidarrContentAlbumsToSend.Count()); var plexEpisodesToSend = FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).Where(x => x.Series.HasTvDb).AsNoTracking(), addedPlexEpisodesLogIds); @@ -117,11 +133,12 @@ namespace Ombi.Schedule.Jobs.Ombi var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie ).OrderByDescending(x => x.AddedAt).Take(10); var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet(); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); - body = await BuildHtml(plexm, embym, plext, embyt, settings); + var lidarr = lidarrContent.OrderByDescending(x => x.AddedAt).Take(10).ToHashSet(); + body = await BuildHtml(plexm, embym, plext, embyt, lidarr, settings); } else { - body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings); + body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, lidarrContentAlbumsToSend, settings); if (body.IsNullOrEmpty()) { return; @@ -298,7 +315,8 @@ namespace Ombi.Schedule.Jobs.Ombi return resolver.ParseMessage(template, curlys); } - private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, HashSet plexEpisodes, HashSet embyEp, NewsletterSettings settings) + private async Task BuildHtml(IQueryable plexContentToSend, IQueryable embyContentToSend, + HashSet plexEpisodes, HashSet embyEp, HashSet albums, NewsletterSettings settings) { var sb = new StringBuilder(); @@ -340,6 +358,24 @@ namespace Ombi.Schedule.Jobs.Ombi sb.Append(""); } + + if (albums.Any() && !settings.DisableMusic) + { + sb.Append("

    New Albums



    "); + sb.Append( + ""); + sb.Append(""); + sb.Append(""); + sb.Append(""); + sb.Append("
    "); + sb.Append(""); + sb.Append(""); + await ProcessAlbums(albums, sb); + sb.Append(""); + sb.Append("
    "); + sb.Append("
    "); + } + return sb.ToString(); } @@ -382,6 +418,40 @@ namespace Ombi.Schedule.Jobs.Ombi } } } + private async Task ProcessAlbums(HashSet albumsToSend, StringBuilder sb) + { + var settings = await _lidarrSettings.GetSettingsAsync(); + int count = 0; + var ordered = albumsToSend.OrderByDescending(x => x.AddedAt); + foreach (var content in ordered) + { + var info = await _lidarrApi.GetAlbumByForeignId(content.ForeignAlbumId, settings.ApiKey, settings.FullUri); + if (info == null) + { + continue; + } + try + { + CreateAlbumHtmlContent(sb, info); + count += 1; + } + catch (Exception e) + { + _log.LogError(e, "Error when Processing Lidarr Album {0}", info.title); + } + finally + { + EndLoopHtml(sb); + } + + if (count == 2) + { + count = 0; + sb.Append(""); + sb.Append(""); + } + } + } private async Task ProcessEmbyMovies(IQueryable embyContent, StringBuilder sb) { @@ -467,6 +537,41 @@ namespace Ombi.Schedule.Jobs.Ombi } } + private void CreateAlbumHtmlContent(StringBuilder sb, AlbumLookup info) + { + var cover = info.images + .FirstOrDefault(x => x.coverType.Equals("cover", StringComparison.InvariantCultureIgnoreCase))?.url; + if (cover.IsNullOrEmpty()) + { + cover = info.remoteCover; + } + AddBackgroundInsideTable(sb, cover); + var disk = info.images + .FirstOrDefault(x => x.coverType.Equals("disc", StringComparison.InvariantCultureIgnoreCase))?.url; + if (disk.IsNullOrEmpty()) + { + disk = info.remoteCover; + } + AddPosterInsideTable(sb, disk); + + AddMediaServerUrl(sb, string.Empty, string.Empty); + AddInfoTable(sb); + + var releaseDate = $"({info.releaseDate.Year})"; + + AddTitle(sb, string.Empty, $"{info.title} {releaseDate}"); + + var summary = info.artist?.artistName ?? string.Empty; + if (summary.Length > 280) + { + summary = summary.Remove(280); + summary = summary + "...

    "; + } + AddParagraph(sb, summary); + + AddGenres(sb, $"Type: {info.albumType}"); + } + private async Task ProcessPlexTv(HashSet plexContent, StringBuilder sb) { var series = new List(); diff --git a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs index e79f3182c..3f6416af5 100644 --- a/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs +++ b/src/Ombi.Settings/Settings/Models/Notifications/NewsletterSettings.cs @@ -6,6 +6,7 @@ namespace Ombi.Settings.Settings.Models.Notifications { public bool DisableTv { get; set; } public bool DisableMovies { get; set; } + public bool DisableMusic { get; set; } public bool Enabled { get; set; } public List ExternalEmails { get; set; } = new List(); } diff --git a/src/Ombi.Store/Entities/LidarrAlbumCache.cs b/src/Ombi.Store/Entities/LidarrAlbumCache.cs index d9ceab8a3..03099face 100644 --- a/src/Ombi.Store/Entities/LidarrAlbumCache.cs +++ b/src/Ombi.Store/Entities/LidarrAlbumCache.cs @@ -13,6 +13,7 @@ namespace Ombi.Store.Entities public bool Monitored { get; set; } public string Title { get; set; } public decimal PercentOfTracks { get; set; } + public DateTime AddedAt { get; set; } [NotMapped] public bool PartiallyAvailable => PercentOfTracks != 100 && PercentOfTracks > 0; diff --git a/src/Ombi.Store/Entities/RecentlyAddedLog.cs b/src/Ombi.Store/Entities/RecentlyAddedLog.cs index 1ef091149..782d89e3f 100644 --- a/src/Ombi.Store/Entities/RecentlyAddedLog.cs +++ b/src/Ombi.Store/Entities/RecentlyAddedLog.cs @@ -11,18 +11,21 @@ namespace Ombi.Store.Entities public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID public int? EpisodeNumber { get; set; } public int? SeasonNumber { get; set; } + public string AlbumId { get; set; } public DateTime AddedAt { get; set; } } public enum RecentlyAddedType { Plex = 0, - Emby = 1 + Emby = 1, + Lidarr = 2 } public enum ContentType { Parent = 0, - Episode = 1 + Episode = 1, + Album = 2, } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html index 43819ad76..c89c2be0a 100644 --- a/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html +++ b/src/Ombi/ClientApp/app/requests/music/musicrequests.component.html @@ -189,7 +189,7 @@
    - +
    diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html index b23a73221..1581c9a35 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -68,13 +68,13 @@
    -
    + + +
    -
    +
    -->
    @@ -93,11 +93,9 @@ | translate }}
    - -
    - + - \ No newline at end of file + + + + + diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts index 0f9a373e2..9dac4aa8b 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output } from "@angular/core"; import { TranslateService } from "@ngx-translate/core"; import { AuthService } from "../../auth/auth.service"; -import { IRequestEngineResult } from "../../interfaces"; +import { IIssueCategory, IRequestEngineResult } from "../../interfaces"; import { ISearchAlbumResult } from "../../interfaces/ISearchMusicResult"; import { NotificationService, RequestService } from "../../services"; @@ -14,7 +14,15 @@ export class AlbumSearchComponent { @Input() public result: ISearchAlbumResult; public engineResult: IRequestEngineResult; - @Input() public defaultPoster: string; + @Input() public defaultPoster: string; + + @Input() public issueCategories: IIssueCategory[]; + @Input() public issuesEnabled: boolean; + public issuesBarVisible = false; + public issueRequestTitle: string; + public issueRequestId: number; + public issueProviderId: string; + public issueCategorySelected: IIssueCategory; @Output() public setSearch = new EventEmitter(); @@ -29,6 +37,14 @@ export class AlbumSearchComponent { this.setSearch.emit(artistId); } + public reportIssue(catId: IIssueCategory, req: ISearchAlbumResult) { + this.issueRequestId = req.id; + this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; + this.issueCategorySelected = catId; + this.issuesBarVisible = true; + this.issueProviderId = req.id.toString(); + } + public request(searchResult: ISearchAlbumResult) { searchResult.requested = true; searchResult.requestProcessing = true; From 21cb5820dd3441c7afbc4826cd3264e1e2570bf6 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 28 Aug 2018 09:32:40 +0100 Subject: [PATCH 377/495] migrations !wip --- .../20180828083219_MusicIssues.Designer.cs | 1091 +++++++++++++++++ .../Migrations/20180828083219_MusicIssues.cs | 33 + .../Migrations/OmbiContextModelSnapshot.cs | 4 + 3 files changed, 1128 insertions(+) create mode 100644 src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs create mode 100644 src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs diff --git a/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs new file mode 100644 index 000000000..52f00c840 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.Designer.cs @@ -0,0 +1,1091 @@ +// +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 +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180828083219_MusicIssues")] + partial class MusicIssues + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + 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.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + 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.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + 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.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + 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.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + 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.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs new file mode 100644 index 000000000..94a06ff18 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180828083219_MusicIssues.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class MusicIssues : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AlbumId", + table: "RecentlyAddedLog", + nullable: true); + + migrationBuilder.AddColumn( + name: "AddedAt", + table: "LidarrAlbumCache", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "AlbumId", + table: "RecentlyAddedLog"); + + migrationBuilder.DropColumn( + name: "AddedAt", + table: "LidarrAlbumCache"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 0e3d1efea..64400e58c 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -249,6 +249,8 @@ namespace Ombi.Store.Migrations b.Property("Id") .ValueGeneratedOnAdd(); + b.Property("AddedAt"); + b.Property("ArtistId"); b.Property("ForeignAlbumId"); @@ -489,6 +491,8 @@ namespace Ombi.Store.Migrations b.Property("AddedAt"); + b.Property("AlbumId"); + b.Property("ContentId"); b.Property("ContentType"); From f709c0acbc32a6d2d2c3e7bad8682877ee76131b Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 28 Aug 2018 09:35:47 +0100 Subject: [PATCH 378/495] Fill in the addedat !wip --- src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs index 9708df589..2e32b6478 100644 --- a/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs +++ b/src/Ombi.Schedule/Jobs/Lidarr/LidarrAlbumSync.cs @@ -67,7 +67,8 @@ namespace Ombi.Schedule.Jobs.Lidarr TrackCount = a.currentRelease.trackCount, Monitored = a.monitored, Title = a.title, - PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m + PercentOfTracks = a.statistics?.percentOfEpisodes ?? 0m, + AddedAt = DateTime.Now, }); } } From 18b48cd0a88d81269da3c45e6cde07b523b99ceb Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 28 Aug 2018 16:19:20 +0100 Subject: [PATCH 379/495] fixed linting !wip --- .../ClientApp/app/search/music/albumsearch.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html index 1581c9a35..fd5f71075 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -45,11 +45,11 @@ - + - + @@ -79,7 +79,7 @@ -
    +
    +
    +
    + + +
    +
    +
    From 28a1886767eab052eb6a71359b61aa214a3e0e5d Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 28 Aug 2018 20:09:54 +0100 Subject: [PATCH 391/495] Made the test button actually work on the Lidarr settings page !wip --- src/Ombi.Api.Lidarr/ILidarrApi.cs | 1 + src/Ombi.Api.Lidarr/LidarrApi.cs | 7 +++++ src/Ombi.Api.Lidarr/Models/LidarrStatus.cs | 31 +++++++++++++++++++ .../services/applications/tester.service.ts | 5 +++ .../app/settings/lidarr/lidarr.component.ts | 4 +-- .../Controllers/External/TesterController.cs | 30 ++++++++++++++++-- 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 src/Ombi.Api.Lidarr/Models/LidarrStatus.cs diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs index 38724f668..15d20fd28 100644 --- a/src/Ombi.Api.Lidarr/ILidarrApi.cs +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -21,5 +21,6 @@ namespace Ombi.Api.Lidarr Task> GetAllAlbumsByArtistId(int artistId, string apiKey, string baseUrl); Task> GetMetadataProfile(string apiKey, string baseUrl); Task> GetLanguageProfile(string apiKey, string baseUrl); + Task Status(string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index cf358699c..2aded7e1a 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -147,6 +147,13 @@ namespace Ombi.Api.Lidarr return Api.Request>(request); } + public Task Status(string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/system/status", baseUrl, HttpMethod.Get); + AddHeaders(request, apiKey); + return Api.Request(request); + } + private void AddHeaders(Request request, string key) { request.AddHeader("X-Api-Key", key); diff --git a/src/Ombi.Api.Lidarr/Models/LidarrStatus.cs b/src/Ombi.Api.Lidarr/Models/LidarrStatus.cs new file mode 100644 index 000000000..27f6c1820 --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/LidarrStatus.cs @@ -0,0 +1,31 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + public class LidarrStatus + { + public string version { get; set; } + public DateTime buildTime { get; set; } + public bool isDebug { get; set; } + public bool isProduction { get; set; } + public bool isAdmin { get; set; } + public bool isUserInteractive { get; set; } + public string startupPath { get; set; } + public string appData { get; set; } + public string osName { get; set; } + public string osVersion { get; set; } + public bool isMonoRuntime { get; set; } + public bool isMono { get; set; } + public bool isLinux { get; set; } + public bool isOsx { get; set; } + public bool isWindows { get; set; } + public string mode { get; set; } + public string branch { get; set; } + public string authentication { get; set; } + public string sqliteVersion { get; set; } + public int migrationVersion { get; set; } + public string urlBase { get; set; } + public string runtimeVersion { get; set; } + public string runtimeName { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/services/applications/tester.service.ts b/src/Ombi/ClientApp/app/services/applications/tester.service.ts index 3fa038888..e692b9196 100644 --- a/src/Ombi/ClientApp/app/services/applications/tester.service.ts +++ b/src/Ombi/ClientApp/app/services/applications/tester.service.ts @@ -11,6 +11,7 @@ import { IDiscordNotifcationSettings, IEmailNotificationSettings, IEmbyServer, + ILidarrSettings, IMattermostNotifcationSettings, IMobileNotificationTestSettings, INewsletterNotificationSettings, @@ -66,6 +67,10 @@ export class TesterService extends ServiceHelpers { return this.http.post(`${this.url}radarr`, JSON.stringify(settings), {headers: this.headers}); } + public lidarrTest(settings: ILidarrSettings): Observable { + return this.http.post(`${this.url}lidarr`, JSON.stringify(settings), {headers: this.headers}); + } + public sonarrTest(settings: ISonarrSettings): Observable { return this.http.post(`${this.url}sonarr`, JSON.stringify(settings), {headers: this.headers}); } diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index 8100c0194..b124f9d47 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -124,8 +124,8 @@ export class LidarrComponent implements OnInit { this.notificationService.error("Please check your entered values"); return; } - const settings = form.value; - this.testerService.radarrTest(settings).subscribe(x => { + const settings = form.value; + this.testerService.lidarrTest(settings).subscribe(x => { if (x === true) { this.notificationService.success("Successfully connected to Lidarr!"); } else { diff --git a/src/Ombi/Controllers/External/TesterController.cs b/src/Ombi/Controllers/External/TesterController.cs index fca42bb63..5e3156bde 100644 --- a/src/Ombi/Controllers/External/TesterController.cs +++ b/src/Ombi/Controllers/External/TesterController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Ombi.Api.CouchPotato; using Ombi.Api.Emby; +using Ombi.Api.Lidarr; using Ombi.Api.Plex; using Ombi.Api.Radarr; using Ombi.Api.SickRage; @@ -38,7 +39,8 @@ namespace Ombi.Controllers.External public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN, IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm, IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger log, IEmailProvider provider, - ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification) + ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification, + ILidarrApi lidarrApi) { Service = service; DiscordNotification = notification; @@ -58,6 +60,7 @@ namespace Ombi.Controllers.External SickRageApi = srApi; Newsletter = newsletter; MobileNotification = mobileNotification; + LidarrApi = lidarrApi; } private INotificationService Service { get; } @@ -78,6 +81,7 @@ namespace Ombi.Controllers.External private ISickRageApi SickRageApi { get; } private INewsletterJob Newsletter { get; } private IMobileNotification MobileNotification { get; } + private ILidarrApi LidarrApi { get; } /// @@ -397,7 +401,7 @@ namespace Ombi.Controllers.External { try { - await MobileNotification.NotifyAsync(new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1, UserId = settings.UserId}, settings.Settings); + await MobileNotification.NotifyAsync(new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1, UserId = settings.UserId }, settings.Settings); return true; } @@ -407,5 +411,27 @@ namespace Ombi.Controllers.External return false; } } + + [HttpPost("lidarr")] + public async Task LidarrTest([FromBody] LidarrSettings settings) + { + try + { + var status = await LidarrApi.Status(settings.ApiKey, settings.FullUri); + if (status != null & status?.version.HasValue() ?? false) + { + return true; + } + else + { + return false; + } + } + catch (Exception e) + { + Log.LogError(LoggingEvents.Api, e, "Could not test Mobile Notifications"); + return false; + } + } } } \ No newline at end of file From 9b3bd1b3fbc01afee259bbb2a3282ff850781310 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 28 Aug 2018 20:12:21 +0100 Subject: [PATCH 392/495] Fixed build !wip --- src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index b124f9d47..3860b5675 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -2,7 +2,6 @@ import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { ILidarrSettings, IMinimumAvailability, IProfiles, IRadarrProfile, IRadarrRootFolder } from "../../interfaces"; -import { IRadarrSettings } from "../../interfaces"; import { LidarrService, TesterService } from "../../services"; import { NotificationService } from "../../services"; import { SettingsService } from "../../services"; From 0ff920fdb3d77a84168ea877196906e6e3924f9d Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 28 Aug 2018 20:26:26 +0100 Subject: [PATCH 393/495] Fixed #2475 --- src/Ombi.Core/Engine/TvSearchEngine.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Ombi.Core/Engine/TvSearchEngine.cs b/src/Ombi.Core/Engine/TvSearchEngine.cs index 253363ec1..bb674a35d 100644 --- a/src/Ombi.Core/Engine/TvSearchEngine.cs +++ b/src/Ombi.Core/Engine/TvSearchEngine.cs @@ -54,7 +54,16 @@ namespace Ombi.Core.Engine if (searchResult != null) { - return await ProcessResults(searchResult); + var retVal = new List(); + foreach (var tvMazeSearch in searchResult) + { + if (tvMazeSearch.show.externals == null || !(tvMazeSearch.show.externals?.thetvdb.HasValue ?? false)) + { + continue; + } + retVal.Add(await ProcessResult(tvMazeSearch)); + } + return retVal; } return null; } @@ -145,12 +154,16 @@ namespace Ombi.Core.Engine var retVal = new List(); foreach (var tvMazeSearch in items) { - var viewT = Mapper.Map(tvMazeSearch); - retVal.Add(await ProcessResult(viewT)); + retVal.Add(await ProcessResult(tvMazeSearch)); } return retVal; } + private async Task ProcessResult(T tvMazeSearch) + { + return Mapper.Map(tvMazeSearch); + } + private async Task ProcessResult(SearchTvShowViewModel item) { item.TheTvDbId = item.Id.ToString(); From 8b5ae726c0057b84efbb2bbfb586088a0f697b00 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 28 Aug 2018 22:08:15 +0100 Subject: [PATCH 394/495] Add the music roles -.- !wip --- src/Ombi.Store/Context/OmbiContext.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Store/Context/OmbiContext.cs b/src/Ombi.Store/Context/OmbiContext.cs index e93cc89ba..2fdbaa996 100644 --- a/src/Ombi.Store/Context/OmbiContext.cs +++ b/src/Ombi.Store/Context/OmbiContext.cs @@ -120,8 +120,8 @@ namespace Ombi.Store.Context Database.ExecuteSqlCommand("VACUUM;"); // Make sure we have the roles - var roles = Roles.Where(x => x.Name == OmbiRoles.ReceivesNewsletter); - if (!roles.Any()) + var newsletterRole = Roles.Where(x => x.Name == OmbiRoles.ReceivesNewsletter); + if (!newsletterRole.Any()) { Roles.Add(new IdentityRole(OmbiRoles.ReceivesNewsletter) { @@ -129,6 +129,19 @@ namespace Ombi.Store.Context }); SaveChanges(); } + var requestMusicRole = Roles.Where(x => x.Name == OmbiRoles.RequestMusic); + if (!requestMusicRole.Any()) + { + Roles.Add(new IdentityRole(OmbiRoles.RequestMusic) + { + NormalizedName = OmbiRoles.RequestMusic.ToUpper() + }); + Roles.Add(new IdentityRole(OmbiRoles.AutoApproveMusic) + { + NormalizedName = OmbiRoles.AutoApproveMusic.ToUpper() + }); + SaveChanges(); + } // Make sure we have the API User var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase)); From a214ecd67ede6f05c2db09eab7ff6f16e14e2660 Mon Sep 17 00:00:00 2001 From: Jamie Date: Tue, 28 Aug 2018 22:12:45 +0100 Subject: [PATCH 395/495] Added more logging into the updater --- src/Ombi.Updater/Installer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Updater/Installer.cs b/src/Ombi.Updater/Installer.cs index 6a1ae9401..10d574a10 100644 --- a/src/Ombi.Updater/Installer.cs +++ b/src/Ombi.Updater/Installer.cs @@ -50,6 +50,7 @@ namespace Ombi.Updater private void StartOmbi(StartupOptions options) { + var startupArgsBuilder = new StringBuilder(); _log.LogDebug("Starting ombi"); var fileName = "Ombi.exe"; if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -73,7 +74,6 @@ namespace Ombi.Updater } else { - var startupArgsBuilder = new StringBuilder(); if (!string.IsNullOrEmpty(options.Host)) { startupArgsBuilder.Append($"--host {options.Host} "); @@ -96,7 +96,10 @@ namespace Ombi.Updater } } - _log.LogDebug("Ombi started, now exiting"); + _log.LogDebug($"Ombi started, now exiting"); + _log.LogDebug($"Working dir: {options.ApplicationPath} (Application Path)"); + _log.LogDebug($"Filename: {Path.Combine(options.ApplicationPath, fileName)}"); + _log.LogDebug($"Startup Args: {startupArgsBuilder.ToString()}"); Environment.Exit(0); } From 5bae5cf096c66a2b9190f376cf9615b332a54656 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 29 Aug 2018 08:13:50 +0100 Subject: [PATCH 396/495] Fixed #2481 --- src/Ombi/ClientApp/app/settings/update/update.component.html | 4 ++-- src/Ombi/ClientApp/app/settings/update/update.component.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/settings/update/update.component.html b/src/Ombi/ClientApp/app/settings/update/update.component.html index c0de9a0a1..e82fcd30d 100644 --- a/src/Ombi/ClientApp/app/settings/update/update.component.html +++ b/src/Ombi/ClientApp/app/settings/update/update.component.html @@ -5,8 +5,8 @@
    Update Settings
    -
    - +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/update/update.component.ts b/src/Ombi/ClientApp/app/settings/update/update.component.ts index b76c9ca22..df0e8b32e 100644 --- a/src/Ombi/ClientApp/app/settings/update/update.component.ts +++ b/src/Ombi/ClientApp/app/settings/update/update.component.ts @@ -63,7 +63,7 @@ export class UpdateComponent implements OnInit { this.notificationService.error("Please check your entered values"); return; } - this.enableUpdateButton = form.value.autoUpdateEnabled || form.value.testMode; + this.enableUpdateButton = form.value.autoUpdateEnabled; this.settingsService.saveUpdateSettings(form.value) .subscribe(x => { if (x) { From f89165314bdff5b5032f5011ef1dc5583b903606 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 29 Aug 2018 11:48:05 +0100 Subject: [PATCH 397/495] Revert request.service.ts to version on upstream/develop --- src/Ombi/ClientApp/app/services/request.service.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 1ebed5dcd..48fa5622d 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -9,22 +9,12 @@ import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests import { ITvRequestViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; -import { IRemainingRequests } from "../interfaces/IRemainingRequests"; - @Injectable() export class RequestService extends ServiceHelpers { constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/Request/", platformLocation); } - public getRemainingMovieRequests(): Observable { - return this.http.get(`${this.url}movie/remaining`, {headers: this.headers}); - } - - public getRemainingTvRequests(): Observable { - return this.http.get(`${this.url}tv/remaining`, {headers: this.headers}); - } - public requestMovie(movie: IMovieRequestModel): Observable { return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); } @@ -38,7 +28,7 @@ export class RequestService extends ServiceHelpers { } public requestTv(tv: ITvRequestViewModel): Observable { - return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }); + return this.http.post(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers}); } public approveMovie(movie: IMovieUpdateModel): Observable { From a23c1030f487145684f0eab1a57101f422a69f6c Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 29 Aug 2018 11:56:51 +0100 Subject: [PATCH 398/495] Revert "Revert request.service.ts to version on upstream/develop" This reverts commit f89165314bdff5b5032f5011ef1dc5583b903606. --- src/Ombi/ClientApp/app/services/request.service.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 48fa5622d..1ebed5dcd 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -9,12 +9,22 @@ import { FilterType, IChildRequests, IFilter, IMovieRequestModel, IMovieRequests import { ITvRequestViewModel } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; +import { IRemainingRequests } from "../interfaces/IRemainingRequests"; + @Injectable() export class RequestService extends ServiceHelpers { constructor(http: HttpClient, public platformLocation: PlatformLocation) { super(http, "/api/v1/Request/", platformLocation); } + public getRemainingMovieRequests(): Observable { + return this.http.get(`${this.url}movie/remaining`, {headers: this.headers}); + } + + public getRemainingTvRequests(): Observable { + return this.http.get(`${this.url}tv/remaining`, {headers: this.headers}); + } + public requestMovie(movie: IMovieRequestModel): Observable { return this.http.post(`${this.url}Movie/`, JSON.stringify(movie), {headers: this.headers}); } @@ -28,7 +38,7 @@ export class RequestService extends ServiceHelpers { } public requestTv(tv: ITvRequestViewModel): Observable { - return this.http.post(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers}); + return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }); } public approveMovie(movie: IMovieUpdateModel): Observable { From 7534a634c24561303c9f829571e2735988df96e5 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 29 Aug 2018 12:02:03 +0100 Subject: [PATCH 399/495] Fix formatting error --- src/Ombi/ClientApp/app/services/request.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/services/request.service.ts b/src/Ombi/ClientApp/app/services/request.service.ts index 1ebed5dcd..ac8769e71 100644 --- a/src/Ombi/ClientApp/app/services/request.service.ts +++ b/src/Ombi/ClientApp/app/services/request.service.ts @@ -38,7 +38,7 @@ export class RequestService extends ServiceHelpers { } public requestTv(tv: ITvRequestViewModel): Observable { - return this.http.post(`${this.url}TV/`, JSON.stringify(tv), { headers: this.headers }); + return this.http.post(`${this.url}TV/`, JSON.stringify(tv), {headers: this.headers}); } public approveMovie(movie: IMovieUpdateModel): Observable { From bcb193f321177665c882fca5ae79fd9d34687a0f Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 29 Aug 2018 13:25:43 +0100 Subject: [PATCH 400/495] made a start !wip --- src/Ombi.Schedule.Tests/NewsletterTests.cs | 2 +- .../Entities/UserNotificationPreferences.cs | 20 +++++++++++++ src/Ombi/Controllers/IdentityController.cs | 29 ++++++++++++++++++- 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 src/Ombi.Store/Entities/UserNotificationPreferences.cs diff --git a/src/Ombi.Schedule.Tests/NewsletterTests.cs b/src/Ombi.Schedule.Tests/NewsletterTests.cs index fcbd35107..b3c2ce98a 100644 --- a/src/Ombi.Schedule.Tests/NewsletterTests.cs +++ b/src/Ombi.Schedule.Tests/NewsletterTests.cs @@ -18,7 +18,7 @@ namespace Ombi.Schedule.Tests var emailSettings = new Mock>(); var customziation = new Mock>(); var newsletterSettings = new Mock>(); - var newsletter = new NewsletterJob(null, null, null, null, null, null, customziation.Object, emailSettings.Object, null, null, newsletterSettings.Object, null); + var newsletter = new NewsletterJob(null, null, null, null, null, null, customziation.Object, emailSettings.Object, null, null, newsletterSettings.Object, null, null, null, null); var ep = new List(); foreach (var i in episodes) diff --git a/src/Ombi.Store/Entities/UserNotificationPreferences.cs b/src/Ombi.Store/Entities/UserNotificationPreferences.cs new file mode 100644 index 000000000..c779480c8 --- /dev/null +++ b/src/Ombi.Store/Entities/UserNotificationPreferences.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; +using Ombi.Helpers; + +namespace Ombi.Store.Entities +{ + [Table(nameof(UserNotificationPreferences))] + public class UserNotificationPreferences : Entity + { + public string UserId { get; set; } + public NotificationAgent Agent { get; set; } + public bool Enabled { get; set; } + public string Value { get; set; } + + [ForeignKey(nameof(UserId))] + public OmbiUser User { get; set; } + } +} diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index 742d75b34..d7e556f26 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -60,7 +60,8 @@ namespace Ombi.Controllers IRepository issueComments, IRepository notificationRepository, IRepository subscriptionRepository, - ISettingsService umSettings) + ISettingsService umSettings, + IRepository notificationPreferences) { UserManager = user; Mapper = mapper; @@ -81,6 +82,7 @@ namespace Ombi.Controllers _requestSubscriptionRepository = subscriptionRepository; _notificationRepository = notificationRepository; _userManagementSettings = umSettings; + _userNotificationPreferences = notificationPreferences; } private OmbiUserManager UserManager { get; } @@ -102,6 +104,7 @@ namespace Ombi.Controllers private readonly IRepository _requestLogRepository; private readonly IRepository _notificationRepository; private readonly IRepository _requestSubscriptionRepository; + private readonly IRepository _userNotificationPreferences; /// @@ -787,6 +790,30 @@ namespace Ombi.Controllers return user.UserAccessToken; } + [HttpGet("notificationpreferences")] + public async Task> GetUserPreferences() + { + //TODO potentially use a view model + var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); + var userPreferences = await _userNotificationPreferences.GetAll().Where(x => x.UserId == user.Id).ToListAsync(); + + var agents = Enum.GetValues(typeof(NotificationAgent)).Cast(); + foreach (var a in agents) + { + var hasAgent = userPreferences.Any(x => x.Agent == a); + if (!hasAgent) + { + // Create the default + userPreferences.Add(new UserNotificationPreferences + { + Agent = a, + }); + } + } + + return userPreferences; + } + private async Task> AddRoles(IEnumerable roles, OmbiUser ombiUser) { var roleResult = new List(); From 6882209e3c82dc51805b41ced78162c6c57e7541 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 29 Aug 2018 17:48:41 +0100 Subject: [PATCH 401/495] Refactor code --- src/Ombi/ClientApp/app/requests/movierequests.component.ts | 3 --- src/Ombi/ClientApp/app/search/moviesearch.component.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/movierequests.component.ts b/src/Ombi/ClientApp/app/requests/movierequests.component.ts index 651637308..311adeb92 100644 --- a/src/Ombi/ClientApp/app/requests/movierequests.component.ts +++ b/src/Ombi/ClientApp/app/requests/movierequests.component.ts @@ -6,7 +6,6 @@ import { debounceTime, distinctUntilChanged } from "rxjs/operators"; import { AuthService } from "../auth/auth.service"; import { FilterType, IFilter, IIssueCategory, IMovieRequests, IPagenator, IRadarrProfile, IRadarrRootFolder, OrderType } from "../interfaces"; -import { IRemainingRequests } from "../interfaces/IRemainingRequests"; import { NotificationService, RadarrService, RequestService } from "../services"; @Component({ @@ -39,8 +38,6 @@ export class MovieRequestsComponent implements OnInit { public orderType: OrderType = OrderType.RequestedDateDesc; public OrderType = OrderType; - public remaining: IRemainingRequests; - public totalMovies: number = 100; private currentlyLoaded: number; private amountToLoad: number; diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 1519941ec..5fd3a37db 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -73,8 +73,8 @@ export class MovieSearchComponent implements OnInit { this.popularMovies(); } - public search(text: any) { + public search(text: any) { this.searchChanged.next(text.target.value); } From 66281d4d9c1b7636401bac70a5030d14661cc8b6 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 29 Aug 2018 18:32:12 +0100 Subject: [PATCH 402/495] Correct path of lidarr component import for unix systems --- src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index 3860b5675..5d85c3d73 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -7,7 +7,7 @@ import { NotificationService } from "../../services"; import { SettingsService } from "../../services"; @Component({ - templateUrl: "./Lidarr.component.html", + templateUrl: "./lidarr.component.html", }) export class LidarrComponent implements OnInit { From 6bf9b3b064d16e314aa84fddbc4f440e1adf4fe5 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 01:40:38 +0100 Subject: [PATCH 403/495] New translations en.json (Portuguese, Brazilian) --- src/Ombi/wwwroot/translations/pt.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Ombi/wwwroot/translations/pt.json b/src/Ombi/wwwroot/translations/pt.json index ba48226a0..5e1e4a75a 100644 --- a/src/Ombi/wwwroot/translations/pt.json +++ b/src/Ombi/wwwroot/translations/pt.json @@ -12,8 +12,8 @@ "Common": { "ContinueButton": "Continuar", "Available": "Disponível", - "PartiallyAvailable": "Partially Available", - "Monitored": "Monitored", + "PartiallyAvailable": "Parcialmente Disponível", + "Monitored": "Monitorado", "NotAvailable": "Inisponível", "ProcessingRequest": "Processando Solicitação", "PendingApproval": "Aprovação Pendente", @@ -76,7 +76,7 @@ "Paragraph": "Quer assistir a algo que não está disponível? Não há problema, basta procurar abaixo e solicitar!", "MoviesTab": "Filmes", "TvTab": "Séries", - "MusicTab": "Music", + "MusicTab": "Músicas", "Suggestions": "Sugestões", "NoResults": "Desculpe, não encontramos nenhum resultado!", "DigitalDate": "Lançamento digital: {{date}}", @@ -114,13 +114,13 @@ "Paragraph": "Abaixo, você pode ver o seu e todos os outros pedidos, bem como o seu download e status de aprovação.", "MoviesTab": "Filmes", "TvTab": "Séries", - "MusicTab": "Music", + "MusicTab": "Músicas", "RequestedBy": "Solicitado por:", "Status": "Status:", "RequestStatus": "Status da solicitação:", "Denied": " Negados:", "TheatricalRelease": "Lançamento nos Cinemas: {{date}}", - "ReleaseDate": "Released: {{date}}", + "ReleaseDate": "Lançado: {{date}}", "TheatricalReleaseSort": "Lançamento nos Cinemas", "DigitalRelease": "Lançamento digital: {{date}}", "RequestDate": "Data da Solicitação:", From 3d5e3cdde9e61b0a5bcf0bbbb75ecda8711e6b1c Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:16 +0100 Subject: [PATCH 404/495] New translations en.json (Danish) --- src/Ombi/wwwroot/translations/da.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/da.json b/src/Ombi/wwwroot/translations/da.json index 184a08fb0..324bfccf1 100644 --- a/src/Ombi/wwwroot/translations/da.json +++ b/src/Ombi/wwwroot/translations/da.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Problemer", From 9a197eb69e67691d77a6977679b272607c02d7c9 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:17 +0100 Subject: [PATCH 405/495] New translations en.json (Dutch) --- src/Ombi/wwwroot/translations/nl.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/nl.json b/src/Ombi/wwwroot/translations/nl.json index 68a8bb606..313d34dcb 100644 --- a/src/Ombi/wwwroot/translations/nl.json +++ b/src/Ombi/wwwroot/translations/nl.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Problemen", From fd39ead3a56bdd340f808b7fd65f0434771c30dc Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:18 +0100 Subject: [PATCH 406/495] New translations en.json (French) --- src/Ombi/wwwroot/translations/fr.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/fr.json b/src/Ombi/wwwroot/translations/fr.json index de6cf49a5..24080f9f7 100644 --- a/src/Ombi/wwwroot/translations/fr.json +++ b/src/Ombi/wwwroot/translations/fr.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Date de la demande ▲", "SortRequestDateDesc": "Date de la demande ▼", "SortStatusAsc": "Statut ▲", - "SortStatusDesc": "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" + } }, "Issues": { "Title": "Problèmes", From 402f29685f412fccac9c8fb51c6d15d9814b901e Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:20 +0100 Subject: [PATCH 407/495] New translations en.json (German) --- src/Ombi/wwwroot/translations/de.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index eaa0009bb..fdd1c6d63 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Probleme", From e30e59dc25be8cce46c61478f5b1c10e20ef63b8 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:21 +0100 Subject: [PATCH 408/495] New translations en.json (Italian) --- src/Ombi/wwwroot/translations/it.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/it.json b/src/Ombi/wwwroot/translations/it.json index 97a4a9dbb..647032425 100644 --- a/src/Ombi/wwwroot/translations/it.json +++ b/src/Ombi/wwwroot/translations/it.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Problemi", From 8fb214c3ff9b0e20e08873a104f68b11fd3a7244 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:23 +0100 Subject: [PATCH 409/495] New translations en.json (Norwegian) --- src/Ombi/wwwroot/translations/no.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/no.json b/src/Ombi/wwwroot/translations/no.json index 27496a68a..b6b162621 100644 --- a/src/Ombi/wwwroot/translations/no.json +++ b/src/Ombi/wwwroot/translations/no.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Mangler", From 7353e03473db1f6d9f5a4329335c2a62d2efa575 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:24 +0100 Subject: [PATCH 410/495] New translations en.json (Polish) --- src/Ombi/wwwroot/translations/pl.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/pl.json b/src/Ombi/wwwroot/translations/pl.json index b39fb7e82..55c8b8060 100644 --- a/src/Ombi/wwwroot/translations/pl.json +++ b/src/Ombi/wwwroot/translations/pl.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Data zgłoszenia ▲", "SortRequestDateDesc": "Data zgłoszenia ▼", "SortStatusAsc": "Stan ▲", - "SortStatusDesc": "Stan ▼" + "SortStatusDesc": "Stan ▼", + "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" + } }, "Issues": { "Title": "Problemy", From f3954accffed6ca324dd8bce07376634406b7140 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:25 +0100 Subject: [PATCH 411/495] New translations en.json (Portuguese, Brazilian) --- src/Ombi/wwwroot/translations/pt.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/pt.json b/src/Ombi/wwwroot/translations/pt.json index 5e1e4a75a..d1d15bcc5 100644 --- a/src/Ombi/wwwroot/translations/pt.json +++ b/src/Ombi/wwwroot/translations/pt.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Data da Solicitação", "SortRequestDateDesc": "Data da Solicitação", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Problemas", From b7c2dabe728f74e8ad89647adc9185960197b6a5 Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:27 +0100 Subject: [PATCH 412/495] New translations en.json (Spanish) --- src/Ombi/wwwroot/translations/es.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/es.json b/src/Ombi/wwwroot/translations/es.json index 7189add03..0dee65f9a 100644 --- a/src/Ombi/wwwroot/translations/es.json +++ b/src/Ombi/wwwroot/translations/es.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Incidencias", From 66adb07957757e54ad07bb177845fe469d21871c Mon Sep 17 00:00:00 2001 From: Jamie Date: Fri, 31 Aug 2018 14:51:28 +0100 Subject: [PATCH 413/495] New translations en.json (Swedish) --- src/Ombi/wwwroot/translations/sv.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/sv.json b/src/Ombi/wwwroot/translations/sv.json index 94c8ee1d8..7885b7e31 100644 --- a/src/Ombi/wwwroot/translations/sv.json +++ b/src/Ombi/wwwroot/translations/sv.json @@ -145,7 +145,14 @@ "SortRequestDateAsc": "Request Date ▲", "SortRequestDateDesc": "Request Date ▼", "SortStatusAsc": "Status ▲", - "SortStatusDesc": "Status ▼" + "SortStatusDesc": "Status ▼", + "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" + } }, "Issues": { "Title": "Problem", From fa7a64e1ad69c20366c88b97d189388c65f635f9 Mon Sep 17 00:00:00 2001 From: Stephen Panzer Date: Fri, 31 Aug 2018 11:42:32 -0600 Subject: [PATCH 414/495] Add clearfix class. Closes #2486 --- .../ClientApp/app/requests/tvrequest-children.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html index 12b093bca..f41b17f51 100644 --- a/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html +++ b/src/Ombi/ClientApp/app/requests/tvrequest-children.component.html @@ -1,6 +1,6 @@ 

    -
    +
    From fbb0978dd857966fb2357b66e64fd32a26b9a7a9 Mon Sep 17 00:00:00 2001 From: Stephen Panzer Date: Fri, 31 Aug 2018 12:27:57 -0600 Subject: [PATCH 415/495] Fix displaying year in issue dialog. Closes #2484 --- src/Ombi/ClientApp/app/search/moviesearch.component.ts | 3 ++- src/Ombi/ClientApp/app/search/tvsearch.component.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Ombi/ClientApp/app/search/moviesearch.component.ts b/src/Ombi/ClientApp/app/search/moviesearch.component.ts index 5fd3a37db..59e9fc1b8 100644 --- a/src/Ombi/ClientApp/app/search/moviesearch.component.ts +++ b/src/Ombi/ClientApp/app/search/moviesearch.component.ts @@ -152,7 +152,8 @@ export class MovieSearchComponent implements OnInit { public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) { this.issueRequestId = req.id; - this.issueRequestTitle = req.title + `(${req.releaseDate.getFullYear})`; + const releaseDate = new Date(req.releaseDate); + this.issueRequestTitle = req.title + ` (${releaseDate.getFullYear()})`; this.issueCategorySelected = catId; this.issuesBarVisible = true; this.issueProviderId = req.id.toString(); diff --git a/src/Ombi/ClientApp/app/search/tvsearch.component.ts b/src/Ombi/ClientApp/app/search/tvsearch.component.ts index 632900091..92d0d549a 100644 --- a/src/Ombi/ClientApp/app/search/tvsearch.component.ts +++ b/src/Ombi/ClientApp/app/search/tvsearch.component.ts @@ -197,7 +197,8 @@ export class TvSearchComponent implements OnInit { public reportIssue(catId: IIssueCategory, req: ISearchTvResult) { this.issueRequestId = req.id; - this.issueRequestTitle = req.title + `(${req.firstAired})`; + const firstAiredDate = new Date(req.firstAired); + this.issueRequestTitle = req.title + ` (${firstAiredDate.getFullYear()})`; this.issueCategorySelected = catId; this.issuesBarVisible = true; this.issueProviderId = req.id.toString(); From 243dc99633698f4b6e91b415aeec4c979f55178e Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sat, 1 Sep 2018 02:26:33 +0100 Subject: [PATCH 416/495] bodge fix test to prevent compile error --- src/Ombi.Schedule.Tests/NewsletterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi.Schedule.Tests/NewsletterTests.cs b/src/Ombi.Schedule.Tests/NewsletterTests.cs index fcbd35107..b3c2ce98a 100644 --- a/src/Ombi.Schedule.Tests/NewsletterTests.cs +++ b/src/Ombi.Schedule.Tests/NewsletterTests.cs @@ -18,7 +18,7 @@ namespace Ombi.Schedule.Tests var emailSettings = new Mock>(); var customziation = new Mock>(); var newsletterSettings = new Mock>(); - var newsletter = new NewsletterJob(null, null, null, null, null, null, customziation.Object, emailSettings.Object, null, null, newsletterSettings.Object, null); + var newsletter = new NewsletterJob(null, null, null, null, null, null, customziation.Object, emailSettings.Object, null, null, newsletterSettings.Object, null, null, null, null); var ep = new List(); foreach (var i in episodes) From 69cf31f66b2904aecfc8462712c7521f9f0d90ea Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Sat, 1 Sep 2018 02:32:04 +0100 Subject: [PATCH 417/495] fix bug causing wrong time to be displayed for next request --- src/Ombi.Core/Engine/MovieRequestEngine.cs | 2 +- src/Ombi.Core/Engine/TvRequestEngine.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 5a4bc51f5..733ba4b4f 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -516,7 +516,7 @@ namespace Ombi.Core.Engine HasLimit = true, Limit = limit, Remaining = count, - NextRequest = oldestRequestedAt.AddDays(7), + NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc), }; } } diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 2d937a377..f9a6e640d 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -652,7 +652,7 @@ namespace Ombi.Core.Engine HasLimit = true, Limit = limit, Remaining = count, - NextRequest = oldestRequestedAt.AddDays(7), + NextRequest = DateTime.SpecifyKind(oldestRequestedAt.AddDays(7), DateTimeKind.Utc), }; } } From db5d0c0b08b856c905356ec00129d1901d99f0f7 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 4 Sep 2018 08:36:15 +0100 Subject: [PATCH 418/495] !wip added the api to trigger an album search --- src/Ombi.Api.Lidarr/ILidarrApi.cs | 1 + src/Ombi.Api.Lidarr/LidarrApi.cs | 8 ++++++++ src/Ombi.Api.Lidarr/Models/CommandResult.cs | 15 +++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 src/Ombi.Api.Lidarr/Models/CommandResult.cs diff --git a/src/Ombi.Api.Lidarr/ILidarrApi.cs b/src/Ombi.Api.Lidarr/ILidarrApi.cs index 15d20fd28..4a23c6200 100644 --- a/src/Ombi.Api.Lidarr/ILidarrApi.cs +++ b/src/Ombi.Api.Lidarr/ILidarrApi.cs @@ -22,5 +22,6 @@ namespace Ombi.Api.Lidarr Task> GetMetadataProfile(string apiKey, string baseUrl); Task> GetLanguageProfile(string apiKey, string baseUrl); Task Status(string apiKey, string baseUrl); + Task AlbumSearch(int[] albumIds, string apiKey, string baseUrl); } } \ No newline at end of file diff --git a/src/Ombi.Api.Lidarr/LidarrApi.cs b/src/Ombi.Api.Lidarr/LidarrApi.cs index 2aded7e1a..8cda49cbf 100644 --- a/src/Ombi.Api.Lidarr/LidarrApi.cs +++ b/src/Ombi.Api.Lidarr/LidarrApi.cs @@ -154,6 +154,14 @@ namespace Ombi.Api.Lidarr return Api.Request(request); } + public Task AlbumSearch(int[] albumIds, string apiKey, string baseUrl) + { + var request = new Request($"{ApiVersion}/command/AlbumSearch", baseUrl, HttpMethod.Post); + request.AddJsonBody(albumIds); + AddHeaders(request, apiKey); + return Api.Request(request); + } + private void AddHeaders(Request request, string key) { request.AddHeader("X-Api-Key", key); diff --git a/src/Ombi.Api.Lidarr/Models/CommandResult.cs b/src/Ombi.Api.Lidarr/Models/CommandResult.cs new file mode 100644 index 000000000..7c6483a6a --- /dev/null +++ b/src/Ombi.Api.Lidarr/Models/CommandResult.cs @@ -0,0 +1,15 @@ +using System; + +namespace Ombi.Api.Lidarr.Models +{ + + public class CommandResult + { + public string name { get; set; } + public DateTime startedOn { get; set; } + public DateTime stateChangeTime { get; set; } + public bool sendUpdatesToClient { get; set; } + public string state { get; set; } + public int id { get; set; } + } +} \ No newline at end of file From 85cbc084630ca4ee158aa803fafb2de47e3b4d6d Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 4 Sep 2018 08:52:42 +0100 Subject: [PATCH 419/495] Fixed the issue if in Radarr we only want to add and monitor, if the movie already exists we search for it. --- src/Ombi.Core/Senders/MovieSender.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Ombi.Core/Senders/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs index e57a5bf2a..aa919e552 100644 --- a/src/Ombi.Core/Senders/MovieSender.cs +++ b/src/Ombi.Core/Senders/MovieSender.cs @@ -123,7 +123,10 @@ namespace Ombi.Core.Senders existingMovie.monitored = true; await RadarrApi.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri); // Search for it - await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri); + if (!settings.AddOnly) + { + await RadarrApi.MovieSearch(new[] {existingMovie.id}, settings.ApiKey, settings.FullUri); + } return new SenderResult { Success = true, Sent = true }; } From 7afcb1749a8a54e53609b05b7dd4f4b9e60f9abc Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 4 Sep 2018 08:55:40 +0100 Subject: [PATCH 420/495] Allow Lidarr to specify if we should search for the album --- src/Ombi.Core/Senders/MusicSender.cs | 4 ++++ .../Settings/Models/External/LidarrSettings.cs | 1 + src/Ombi/ClientApp/app/interfaces/ISettings.ts | 1 + .../app/settings/lidarr/lidarr.component.html | 10 ++++++++++ .../ClientApp/app/settings/lidarr/lidarr.component.ts | 1 + 5 files changed, 17 insertions(+) diff --git a/src/Ombi.Core/Senders/MusicSender.cs b/src/Ombi.Core/Senders/MusicSender.cs index 5e1e44126..937204be5 100644 --- a/src/Ombi.Core/Senders/MusicSender.cs +++ b/src/Ombi.Core/Senders/MusicSender.cs @@ -116,6 +116,10 @@ namespace Ombi.Core.Senders } var result = await _lidarrApi.MontiorAlbum(album.id, settings.ApiKey, settings.FullUri); + if (!settings.AddOnly) + { + await _lidarrApi.AlbumSearch(new[] {result.id}, settings.ApiKey, settings.FullUri); + } if (result.monitored) { return new SenderResult {Message = "Album has been requested!", Sent = true, Success = true}; diff --git a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs index e0bdbdc43..3a37b7d43 100644 --- a/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs +++ b/src/Ombi.Settings/Settings/Models/External/LidarrSettings.cs @@ -11,5 +11,6 @@ namespace Ombi.Settings.Settings.Models.External public bool AlbumFolder { get; set; } public int LanguageProfileId { get; set; } public int MetadataProfileId { get; set; } + public bool AddOnly { get; set; } } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/interfaces/ISettings.ts b/src/Ombi/ClientApp/app/interfaces/ISettings.ts index 2fb46a2b7..db4db935e 100644 --- a/src/Ombi/ClientApp/app/interfaces/ISettings.ts +++ b/src/Ombi/ClientApp/app/interfaces/ISettings.ts @@ -93,6 +93,7 @@ export interface ILidarrSettings extends IExternalSettings { metadataProfileId: number; languageProfileId: number; albumFolder: boolean; + addOnly: boolean; } export interface ILandingPageSettings extends ISettings { diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html index 11d9baf2d..c834fbdbd 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.html @@ -3,6 +3,10 @@
    Lidarr Settings +
    + Advanced + +
    @@ -110,6 +114,12 @@
    +
    +
    + + +
    +
    diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index 5d85c3d73..1f372546a 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -45,6 +45,7 @@ export class LidarrComponent implements OnInit { albumFolder: [x.albumFolder], languageProfileId: [x.languageProfileId, [Validators.required]], metadataProfileId: [x.metadataProfileId, [Validators.required]], + addOnly: [x.addOnly] }); if (x.defaultQualityProfile) { From cfd9deb17edde4c0632f57611dc29874db96c222 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 4 Sep 2018 09:29:32 +0100 Subject: [PATCH 421/495] !wip fixed linting --- src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts index 1f372546a..d1e28285f 100644 --- a/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts +++ b/src/Ombi/ClientApp/app/settings/lidarr/lidarr.component.ts @@ -45,7 +45,7 @@ export class LidarrComponent implements OnInit { albumFolder: [x.albumFolder], languageProfileId: [x.languageProfileId, [Validators.required]], metadataProfileId: [x.metadataProfileId, [Validators.required]], - addOnly: [x.addOnly] + addOnly: [x.addOnly], }); if (x.defaultQualityProfile) { From 9c574fb7e901ac16fd9c76e1a5dd3744140632f7 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 5 Sep 2018 00:12:19 +0100 Subject: [PATCH 422/495] Add quota fields to user view model --- .../Engine/Interfaces/IRequestEngine.cs | 2 +- src/Ombi.Core/Engine/MovieRequestEngine.cs | 8 ++++++-- src/Ombi.Core/Engine/TvRequestEngine.cs | 8 ++++++-- src/Ombi.Core/Models/UI/UserViewModel.cs | 2 ++ src/Ombi/ClientApp/app/interfaces/IUser.ts | 4 ++++ .../usermanagement-add.component.ts | 2 ++ src/Ombi/Controllers/IdentityController.cs | 20 +++++++++++++++++-- 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs index 53111fd95..c8b7746f0 100644 --- a/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/IRequestEngine.cs @@ -23,6 +23,6 @@ namespace Ombi.Core.Engine.Interfaces Task GetTotal(); Task UnSubscribeRequest(int requestId, RequestType type); Task SubscribeToRequest(int requestId, RequestType type); - Task GetRemainingRequests(); + Task GetRemainingRequests(OmbiUser user = null); } } \ No newline at end of file diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 733ba4b4f..0cce7af58 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -486,9 +486,13 @@ namespace Ombi.Core.Engine return new RequestEngineResult {Result = true, Message = $"{movieName} has been successfully added!"}; } - public async Task GetRemainingRequests() + public async Task GetRemainingRequests(OmbiUser user) { - OmbiUser user = await GetUser(); + if (user == null) + { + user = await GetUser(); + } + int limit = user.MovieRequestLimit ?? 0; if (limit <= 0) diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index f9a6e640d..a700035a0 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -615,9 +615,13 @@ namespace Ombi.Core.Engine return new RequestEngineResult { Result = true }; } - public async Task GetRemainingRequests() + public async Task GetRemainingRequests(OmbiUser user) { - OmbiUser user = await GetUser(); + if (user == null) + { + user = await GetUser(); + } + int limit = user.EpisodeRequestLimit ?? 0; if (limit <= 0) diff --git a/src/Ombi.Core/Models/UI/UserViewModel.cs b/src/Ombi.Core/Models/UI/UserViewModel.cs index 1c1e6162b..cddc32b90 100644 --- a/src/Ombi.Core/Models/UI/UserViewModel.cs +++ b/src/Ombi.Core/Models/UI/UserViewModel.cs @@ -16,6 +16,8 @@ namespace Ombi.Core.Models.UI public UserType UserType { get; set; } public int MovieRequestLimit { get; set; } public int EpisodeRequestLimit { get; set; } + public RequestQuotaCountModel EpisodeRequestQuota { get; set; } + public RequestQuotaCountModel MovieRequestQuota { get; set; } } public class ClaimCheckboxes diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index cd96848fb..9e14dcfb4 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -1,4 +1,5 @@ import { ICheckbox } from "."; +import { IRemainingRequests } from "./IRemainingRequests"; export interface IUser { id: string; @@ -13,7 +14,10 @@ export interface IUser { movieRequestLimit: number; episodeRequestLimit: number; userAccessToken: string; + // FOR UI + episodeRequestQuota: IRemainingRequests | null; + movieRequestQuota: IRemainingRequests | null; checked: boolean; } diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts index 36b187e79..5e0799552 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts @@ -32,6 +32,8 @@ export class UserManagementAddComponent implements OnInit { episodeRequestLimit: 0, movieRequestLimit: 0, userAccessToken: "", + episodeRequestQuota: null, + movieRequestQuota: null, }; } diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index 742d75b34..3ecb9c3e7 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -16,6 +16,7 @@ using Ombi.Api.Plex; using Ombi.Attributes; using Ombi.Config; using Ombi.Core.Authentication; +using Ombi.Core.Engine.Interfaces; using Ombi.Core.Helpers; using Ombi.Core.Models.UI; using Ombi.Core.Settings; @@ -60,7 +61,9 @@ namespace Ombi.Controllers IRepository issueComments, IRepository notificationRepository, IRepository subscriptionRepository, - ISettingsService umSettings) + ISettingsService umSettings, + IMovieRequestEngine movieRequestEngine, + ITvRequestEngine tvRequestEngine) { UserManager = user; Mapper = mapper; @@ -81,6 +84,8 @@ namespace Ombi.Controllers _requestSubscriptionRepository = subscriptionRepository; _notificationRepository = notificationRepository; _userManagementSettings = umSettings; + TvRequestEngine = tvRequestEngine; + MovieRequestEngine = movieRequestEngine; } private OmbiUserManager UserManager { get; } @@ -94,6 +99,8 @@ namespace Ombi.Controllers private IWelcomeEmail WelcomeEmail { get; } private IMovieRequestRepository MovieRepo { get; } private ITvRequestRepository TvRepo { get; } + private IMovieRequestEngine MovieRequestEngine { get; } + private ITvRequestEngine TvRequestEngine { get; } private readonly ILogger _log; private readonly IPlexApi _plexApi; private readonly ISettingsService _plexSettings; @@ -103,7 +110,6 @@ namespace Ombi.Controllers private readonly IRepository _notificationRepository; private readonly IRepository _requestSubscriptionRepository; - /// /// This is what the Wizard will call when creating the user for the very first time. /// This should never be called after this. @@ -316,6 +322,16 @@ namespace Ombi.Controllers }); } + if (vm.EpisodeRequestLimit > 0) + { + vm.EpisodeRequestQuota = await TvRequestEngine.GetRemainingRequests(user); + } + + if (vm.MovieRequestLimit > 0) + { + vm.MovieRequestQuota = await MovieRequestEngine.GetRemainingRequests(user); + } + return vm; } From 30c9de818b9a93766a0fdb7145cbe14f0865c13f Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 5 Sep 2018 18:48:03 +0100 Subject: [PATCH 423/495] Add html for displaying remaining requests on users page --- .../usermanagement.component.html | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index 519db023f..98bc8749b 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -48,6 +48,12 @@ Roles + + Requests Remaining + + + Next Request Due + Last Logged In @@ -85,6 +91,22 @@
    + +
    + Movies: {{u.movieRequestQuota.remaining}}/{{u.movieRequestLimit}} remaining +
    +
    + TV: {{u.episodeRequestQuota.remaining}}/{{u.episodeRequestLimit}} remaining +
    + + +
    + Movie: {{u.movieRequestQuota.nextRequest | date: 'short'}} +
    +
    + TV: {{u.episodeRequestQuota.nextRequest | date: 'short'}} +
    + {{u.lastLoggedIn | date: 'short'}} From c9b6f5f05eb56f108eac7beff797892fe9f6da26 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 5 Sep 2018 20:25:51 +0100 Subject: [PATCH 424/495] Add to translations --- src/Ombi.Core/Engine/MovieRequestEngine.cs | 6 ++++++ src/Ombi.Core/Engine/TvRequestEngine.cs | 6 ++++++ .../app/usermanagement/usermanagement.component.html | 8 ++++---- .../ClientApp/app/usermanagement/usermanagement.module.ts | 3 +++ src/Ombi/wwwroot/translations/en.json | 6 ++++++ 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 0cce7af58..9fd6033bf 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -491,6 +491,12 @@ namespace Ombi.Core.Engine if (user == null) { user = await GetUser(); + + // If user is still null after attempting to get the logged in user, return null. + if (user == null) + { + return null; + } } int limit = user.MovieRequestLimit ?? 0; diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index a700035a0..6a7f370d9 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -620,6 +620,12 @@ namespace Ombi.Core.Engine if (user == null) { user = await GetUser(); + + // If user is still null after attempting to get the logged in user, return null. + if (user == null) + { + return null; + } } int limit = user.EpisodeRequestLimit ?? 0; diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index 98bc8749b..431ea7ddf 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -93,18 +93,18 @@
    - Movies: {{u.movieRequestQuota.remaining}}/{{u.movieRequestLimit}} remaining + {{'UserManagment.MovieRemaining' | translate: {remaining: u.movieRequestQuota.remaining, total: u.movieRequestLimit} }}
    - TV: {{u.episodeRequestQuota.remaining}}/{{u.episodeRequestLimit}} remaining + {{'UserManagment.TvRemaining' | translate: {remaining: u.episodeRequestQuota.remaining, total: u.episodeRequestLimit} }}
    - Movie: {{u.movieRequestQuota.nextRequest | date: 'short'}} + {{'UserManagment.MovieDue' | translate: {date: (u.movieRequestQuota.nextRequest | date: 'short')} }}
    - TV: {{u.episodeRequestQuota.nextRequest | date: 'short'}} + {{'UserManagment.TvDue' | translate: {date: (u.episodeRequestQuota.nextRequest | date: 'short')} }}
    diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts index cf25446f5..1a91cf295 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts @@ -19,6 +19,8 @@ import { AuthGuard } from "../auth/auth.guard"; import { OrderModule } from "ngx-order-pipe"; import { AddPlexUserComponent } from "./addplexuser.component"; +import { SharedModule } from "../shared/shared.module"; + const routes: Routes = [ { path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, { path: "add", component: UserManagementAddComponent, canActivate: [AuthGuard] }, @@ -39,6 +41,7 @@ const routes: Routes = [ TooltipModule, OrderModule, SidebarModule, + SharedModule, ], declarations: [ UserManagementComponent, diff --git a/src/Ombi/wwwroot/translations/en.json b/src/Ombi/wwwroot/translations/en.json index b38f5fb95..c53229c3d 100644 --- a/src/Ombi/wwwroot/translations/en.json +++ b/src/Ombi/wwwroot/translations/en.json @@ -184,5 +184,11 @@ "FilterHeaderRequestStatus":"Status", "Approved":"Approved", "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } From 5c95cf85e11bc96e4941f9cd797e746181837aab Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 5 Sep 2018 20:40:48 +0100 Subject: [PATCH 425/495] New translations en.json (German) --- src/Ombi/wwwroot/translations/de.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index fdd1c6d63..c3e77d594 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -12,7 +12,7 @@ "Common": { "ContinueButton": "Weiter", "Available": "Verfügbar", - "PartiallyAvailable": "Partially Available", + "PartiallyAvailable": "Teilweise verfügbar", "Monitored": "Monitored", "NotAvailable": "Nicht verfügbar", "ProcessingRequest": "Anfrage wird bearbeitet", @@ -111,7 +111,7 @@ }, "Requests": { "Title": "Anfragen", - "Paragraph": "Unten sehen Sie Ihre und alle anderen Anfragen, sowie deren Download-und Genehmigungsstatus.", + "Paragraph": "Unten sehen Sie Ihre und alle anderen Anfragen, sowie deren Download und Genehmigungsstatus.", "MoviesTab": "Filme", "TvTab": "Serien", "MusicTab": "Music", From 366fa5edaac529137243fa31a66d06ad401ca9c9 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 5 Sep 2018 20:51:14 +0100 Subject: [PATCH 426/495] New translations en.json (German) --- src/Ombi/wwwroot/translations/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index c3e77d594..0a3dc2120 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -120,7 +120,7 @@ "RequestStatus": "Anfrage Status:", "Denied": " Abgelehnt:", "TheatricalRelease": "Theatrical Release: {{date}}", - "ReleaseDate": "Released: {{date}}", + "ReleaseDate": "Veröffentlicht: {{date}}", "TheatricalReleaseSort": "Theatrical Release", "DigitalRelease": "Digital Release: {{date}}", "RequestDate": "Datum der Anfrage:", From d081526e3f534c9d0d43ff7f7fd394ea95902d48 Mon Sep 17 00:00:00 2001 From: Kenton Royal Date: Wed, 5 Sep 2018 20:56:41 +0100 Subject: [PATCH 427/495] Fix bug in which requested TV wasn't logging for some users --- src/Ombi.Core/Engine/TvRequestEngine.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index 6a7f370d9..7994b23aa 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -588,6 +588,15 @@ namespace Ombi.Core.Engine NotificationHelper.NewRequest(model); } + await _requestLog.Add(new RequestLog + { + UserId = (await GetUser()).Id, + RequestDate = DateTime.UtcNow, + RequestId = model.Id, + RequestType = RequestType.TvShow, + EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(), + }); + if (model.Approved) { // Autosend @@ -603,15 +612,6 @@ namespace Ombi.Core.Engine }; } - await _requestLog.Add(new RequestLog - { - UserId = (await GetUser()).Id, - RequestDate = DateTime.UtcNow, - RequestId = model.Id, - RequestType = RequestType.TvShow, - EpisodeCount = model.SeasonRequests.Select(m => m.Episodes.Count).Sum(), - }); - return new RequestEngineResult { Result = true }; } From 9484c84488fe8e1f769b023f4df8459d96a33364 Mon Sep 17 00:00:00 2001 From: Stephen Panzer Date: Sat, 8 Sep 2018 15:06:51 -0600 Subject: [PATCH 428/495] Add href to a tags so that a pointer cursor shows on requests page --- src/Ombi/ClientApp/app/requests/request.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Ombi/ClientApp/app/requests/request.component.html b/src/Ombi/ClientApp/app/requests/request.component.html index 45509fba3..d2df3c97a 100644 --- a/src/Ombi/ClientApp/app/requests/request.component.html +++ b/src/Ombi/ClientApp/app/requests/request.component.html @@ -3,14 +3,14 @@ From 8445b458bedb74e5b3e0d66e326c14c4bce2cba2 Mon Sep 17 00:00:00 2001 From: Jamie Date: Mon, 10 Sep 2018 14:52:26 +0100 Subject: [PATCH 429/495] New translations en.json (Swedish) --- src/Ombi/wwwroot/translations/sv.json | 90 +++++++++++++-------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/src/Ombi/wwwroot/translations/sv.json b/src/Ombi/wwwroot/translations/sv.json index 7885b7e31..b512b2568 100644 --- a/src/Ombi/wwwroot/translations/sv.json +++ b/src/Ombi/wwwroot/translations/sv.json @@ -12,8 +12,8 @@ "Common": { "ContinueButton": "Fortsätt", "Available": "Tillgänglig", - "PartiallyAvailable": "Partially Available", - "Monitored": "Monitored", + "PartiallyAvailable": "Delvis tillgänliga", + "Monitored": "Övervakad", "NotAvailable": "Finns ej", "ProcessingRequest": "Bearbetar förfrågan", "PendingApproval": "Väntar på godkännande", @@ -23,7 +23,7 @@ "Request": "Begär", "Denied": "Nekad", "Approve": "Godkän", - "PartlyAvailable": "Partly Available", + "PartlyAvailable": "Delvis tillgänglig", "Errors": { "Validation": "Vänligen kontrollera din angivna värden" } @@ -65,26 +65,26 @@ "Danish": "Danska", "Dutch": "Holländska", "Norwegian": "Norska", - "BrazillianPortuguese": "Brazillian Portuguese", - "Polish": "Polish" + "BrazillianPortuguese": "Brazillian portugisiska", + "Polish": "Polska" }, "OpenMobileApp": "Öppna Mobil App", - "RecentlyAdded": "Recently Added" + "RecentlyAdded": "Nyligen tillagda" }, "Search": { "Title": "Sök", "Paragraph": "Vill du titta på något som inte är tillgängligt? Inga problem, Sök efter det nedan och önska det!", "MoviesTab": "Filmer", "TvTab": "TV-serier", - "MusicTab": "Music", + "MusicTab": "Musik", "Suggestions": "Förslag", "NoResults": "Tyvärr, hittade vi inte några resultat!", - "DigitalDate": "Digital Release: {{date}}", - "TheatricalRelease": "Theatrical Release: {{date}}", + "DigitalDate": "Digitalt släpp: {{date}}", + "TheatricalRelease": "Biopremiär: {{date}}", "ViewOnPlex": "Visa på Plex", "ViewOnEmby": "Visa på Emby", "RequestAdded": "Efterfrågan om {{title}} har lagts till", - "Similar": "Similar", + "Similar": "Liknande", "Movies": { "PopularMovies": "Populära filmer", "UpcomingMovies": "Kommande filmer", @@ -94,19 +94,19 @@ "Trailer": "Trailer" }, "TvShows": { - "Popular": "Popular", - "Trending": "Trending", - "MostWatched": "Most Watched", - "MostAnticipated": "Most Anticipated", - "Results": "Results", - "AirDate": "Air Date:", - "AllSeasons": "All Seasons", - "FirstSeason": "First Season", - "LatestSeason": "Latest Season", - "Select": "Select ...", - "SubmitRequest": "Submit Request", - "Season": "Season: {{seasonNumber}}", - "SelectAllInSeason": "Select All in Season {{seasonNumber}}" + "Popular": "Populära", + "Trending": "Trendar", + "MostWatched": "Mest sedda", + "MostAnticipated": "Mest efterlängtade", + "Results": "Resultat", + "AirDate": "Sändningsdatum:", + "AllSeasons": "Alla Säsonger", + "FirstSeason": "Första säsongen", + "LatestSeason": "Senaste säsongen", + "Select": "Välj...", + "SubmitRequest": "Skicka förfrågan", + "Season": "Säsong: {{seasonNumber}}", + "SelectAllInSeason": "Välj alla i denna säsong {{seasonNumber}}" } }, "Requests": { @@ -114,15 +114,15 @@ "Paragraph": "Nedan kan du se din och andras efterfrågningar, samt nedladdnings och godkännande status.", "MoviesTab": "Filmer", "TvTab": "TV-serier", - "MusicTab": "Music", + "MusicTab": "Musik", "RequestedBy": "Efterfrågats av:", "Status": "Status:", "RequestStatus": "Status för efterfrågan:", "Denied": " Nekad:", - "TheatricalRelease": "Theatrical Release: {{date}}", - "ReleaseDate": "Released: {{date}}", - "TheatricalReleaseSort": "Theatrical Release", - "DigitalRelease": "Digital Release: {{date}}", + "TheatricalRelease": "Biopremiär: {{date}}", + "ReleaseDate": "Släppt: {{date}}", + "TheatricalReleaseSort": "Biopremiär", + "DigitalRelease": "Digitalt Releasedatum: {{date}}", "RequestDate": "Datum för efterfrågan:", "QualityOverride": "Kvalité överskridande:", "RootFolderOverride": "Root mapp överskridande:", @@ -136,22 +136,22 @@ "GridTitle": "Titel", "AirDate": "Sändningsdatum", "GridStatus": "Status", - "ReportIssue": "Report Issue", - "Filter": "Filter", - "Sort": "Sort", - "SeasonNumberHeading": "Season: {seasonNumber}", - "SortTitleAsc": "Title ▲", - "SortTitleDesc": "Title ▼", - "SortRequestDateAsc": "Request Date ▲", - "SortRequestDateDesc": "Request Date ▼", + "ReportIssue": "Rapportera Problem", + "Filter": "Filtrera", + "Sort": "Sortera", + "SeasonNumberHeading": "Säsong: {seasonNumber}", + "SortTitleAsc": "Titel ▲", + "SortTitleDesc": "Titel ▼", + "SortRequestDateAsc": "Efterfrågades ▲", + "SortRequestDateDesc": "Efterfrågades ▼", "SortStatusAsc": "Status ▲", "SortStatusDesc": "Status ▼", "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}} återstående förfrågningar", + "NextDays": "En annan begäran kommer att läggas till om {{time}} Dagar", + "NextHours": "En annan begäran kommer att läggas till om {{time}} Timmar", + "NextMinutes": "En annan begäran kommer att läggas till om {{time}} Minuter", + "NextMinute": "En annan begäran kommer att läggas till om {{time}} Minut" } }, "Issues": { @@ -174,10 +174,10 @@ "ReportedBy": "Rapporterad av" }, "Filter": { - "ClearFilter": "Clear Filter", - "FilterHeaderAvailability": "Availability", + "ClearFilter": "Rensa filter", + "FilterHeaderAvailability": "Tillgänglighet", "FilterHeaderRequestStatus": "Status", - "Approved": "Approved", - "PendingApproval": "Pending Approval" + "Approved": "Godkänd", + "PendingApproval": "Väntar på godkännande" } } \ No newline at end of file From 7d50a845ccbfc7d8e1ed6bc940b211d2d8d3bd27 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 12 Sep 2018 13:22:15 +0100 Subject: [PATCH 430/495] Users can now see the music search tab #2493 --- src/Ombi/ClientApp/app/search/search.component.ts | 2 +- src/Ombi/ClientApp/app/services/settings.service.ts | 4 ++++ src/Ombi/Controllers/SettingsController.cs | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/search/search.component.ts b/src/Ombi/ClientApp/app/search/search.component.ts index 74221e71c..43d926970 100644 --- a/src/Ombi/ClientApp/app/search/search.component.ts +++ b/src/Ombi/ClientApp/app/search/search.component.ts @@ -20,7 +20,7 @@ export class SearchComponent implements OnInit { } public ngOnInit() { - this.settingsService.getLidarr().subscribe(x => this.musicEnabled = x.enabled); + this.settingsService.lidarrEnabled().subscribe(x => this.musicEnabled = x); this.showMovie = true; this.showTv = false; this.showMusic = false; diff --git a/src/Ombi/ClientApp/app/services/settings.service.ts b/src/Ombi/ClientApp/app/services/settings.service.ts index 2016d10b7..b4611bfa8 100644 --- a/src/Ombi/ClientApp/app/services/settings.service.ts +++ b/src/Ombi/ClientApp/app/services/settings.service.ts @@ -96,6 +96,10 @@ export class SettingsService extends ServiceHelpers { return this.http.get(`${this.url}/Lidarr`, {headers: this.headers}); } + public lidarrEnabled(): Observable { + return this.http.get(`${this.url}/lidarrenabled`, {headers: this.headers}); + } + public saveLidarr(settings: ILidarrSettings): Observable { return this.http.post(`${this.url}/Lidarr`, JSON.stringify(settings), {headers: this.headers}); } diff --git a/src/Ombi/Controllers/SettingsController.cs b/src/Ombi/Controllers/SettingsController.cs index 84192c53c..474ae8823 100644 --- a/src/Ombi/Controllers/SettingsController.cs +++ b/src/Ombi/Controllers/SettingsController.cs @@ -328,6 +328,18 @@ namespace Ombi.Controllers return await Get(); } + /// + /// Gets the Lidarr Settings. + /// + /// + [HttpGet("lidarrenabled")] + [AllowAnonymous] + public async Task LidarrEnabled() + { + var settings = await Get(); + return settings.Enabled; + } + /// /// Save the Lidarr settings. /// From f6dd41918de499bd2a9e33b1a8c8dadc03f168bd Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:39 +0100 Subject: [PATCH 431/495] New translations en.json (Danish) --- src/Ombi/wwwroot/translations/da.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/da.json b/src/Ombi/wwwroot/translations/da.json index 324bfccf1..61f8c718d 100644 --- a/src/Ombi/wwwroot/translations/da.json +++ b/src/Ombi/wwwroot/translations/da.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Godkendt", "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From 766a63261b3bd16d324aced38d248417fdfa234d Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:41 +0100 Subject: [PATCH 432/495] New translations en.json (Dutch) --- src/Ombi/wwwroot/translations/nl.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/nl.json b/src/Ombi/wwwroot/translations/nl.json index 313d34dcb..c32195922 100644 --- a/src/Ombi/wwwroot/translations/nl.json +++ b/src/Ombi/wwwroot/translations/nl.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Goedgekeurd", "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From f40f73a2238f9d7801dcb7658f3ec0cb88f338ee Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:43 +0100 Subject: [PATCH 433/495] New translations en.json (French) --- src/Ombi/wwwroot/translations/fr.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/fr.json b/src/Ombi/wwwroot/translations/fr.json index 24080f9f7..7c5201aa9 100644 --- a/src/Ombi/wwwroot/translations/fr.json +++ b/src/Ombi/wwwroot/translations/fr.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Statut", "Approved": "Validée", "PendingApproval": "En attente de validation" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From 3b3dbc3d8000eb5383d5079f111555d9c3ab6aec Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:44 +0100 Subject: [PATCH 434/495] New translations en.json (German) --- src/Ombi/wwwroot/translations/de.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/de.json b/src/Ombi/wwwroot/translations/de.json index 0a3dc2120..219de654e 100644 --- a/src/Ombi/wwwroot/translations/de.json +++ b/src/Ombi/wwwroot/translations/de.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Bestätigt", "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From 01f104058c0f30eb8f099be9e6945718389e8c0c Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:45 +0100 Subject: [PATCH 435/495] New translations en.json (Italian) --- src/Ombi/wwwroot/translations/it.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/it.json b/src/Ombi/wwwroot/translations/it.json index 647032425..fc9cec63d 100644 --- a/src/Ombi/wwwroot/translations/it.json +++ b/src/Ombi/wwwroot/translations/it.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Approved", "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From e58d07d4a552ef7b01fd54f8288564e008819a90 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:47 +0100 Subject: [PATCH 436/495] New translations en.json (Norwegian) --- src/Ombi/wwwroot/translations/no.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/no.json b/src/Ombi/wwwroot/translations/no.json index b6b162621..58c5c93c2 100644 --- a/src/Ombi/wwwroot/translations/no.json +++ b/src/Ombi/wwwroot/translations/no.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Godkjent", "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From c7983887036dddf80af7ec2d4bc34d4e68a4b024 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:48 +0100 Subject: [PATCH 437/495] New translations en.json (Polish) --- src/Ombi/wwwroot/translations/pl.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/pl.json b/src/Ombi/wwwroot/translations/pl.json index 55c8b8060..5378c8334 100644 --- a/src/Ombi/wwwroot/translations/pl.json +++ b/src/Ombi/wwwroot/translations/pl.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Stan", "Approved": "Zatwierdzone", "PendingApproval": "Oczekujące na zatwierdzenie" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From 2640b31231ae54346db5f1345dc4c21debed2ecd Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:49 +0100 Subject: [PATCH 438/495] New translations en.json (Portuguese, Brazilian) --- src/Ombi/wwwroot/translations/pt.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/pt.json b/src/Ombi/wwwroot/translations/pt.json index d1d15bcc5..62b5d8b1b 100644 --- a/src/Ombi/wwwroot/translations/pt.json +++ b/src/Ombi/wwwroot/translations/pt.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Aprovado", "PendingApproval": "Aprovação Pendente" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From edd7ff4c44e6d5a576cea87c0e5c518e34f2dd23 Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:51 +0100 Subject: [PATCH 439/495] New translations en.json (Spanish) --- src/Ombi/wwwroot/translations/es.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/es.json b/src/Ombi/wwwroot/translations/es.json index 0dee65f9a..d99050613 100644 --- a/src/Ombi/wwwroot/translations/es.json +++ b/src/Ombi/wwwroot/translations/es.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Approved", "PendingApproval": "Pending Approval" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From a5a50530e18b016873a1702c079b7e4b5fb8f08d Mon Sep 17 00:00:00 2001 From: Jamie Date: Wed, 12 Sep 2018 13:31:52 +0100 Subject: [PATCH 440/495] New translations en.json (Swedish) --- src/Ombi/wwwroot/translations/sv.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Ombi/wwwroot/translations/sv.json b/src/Ombi/wwwroot/translations/sv.json index b512b2568..4d2ee773c 100644 --- a/src/Ombi/wwwroot/translations/sv.json +++ b/src/Ombi/wwwroot/translations/sv.json @@ -179,5 +179,11 @@ "FilterHeaderRequestStatus": "Status", "Approved": "Godkänd", "PendingApproval": "Väntar på godkännande" + }, + "UserManagment": { + "TvRemaining": "TV: {{remaining}}/{{total}} remaining", + "MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", + "TvDue": "TV: {{date}}", + "MovieDue": "Movie: {{date}}" } } \ No newline at end of file From 9903b17764cda764635a173b581b5f7cdbe8a6cf Mon Sep 17 00:00:00 2001 From: TidusJar Date: Thu, 13 Sep 2018 11:40:59 +0100 Subject: [PATCH 441/495] Update the .net core packages to fix "CVE-2018-8409: ASP.NET Core Denial Of Service Vulnerability" --- src/Ombi/Ombi.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 6640a8eb8..f01e7c05d 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -71,7 +71,7 @@ - + From 8915e64ff36f8173dd3a2830b98a353f1efffa7e Mon Sep 17 00:00:00 2001 From: TidusJar Date: Thu, 13 Sep 2018 11:45:19 +0100 Subject: [PATCH 442/495] Updated all the MS packages --- src/Ombi.Core/Ombi.Core.csproj | 2 +- .../Ombi.DependencyInjection.csproj | 4 ++-- src/Ombi.Helpers/Ombi.Helpers.csproj | 2 +- src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj | 2 +- src/Ombi.Store/Ombi.Store.csproj | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ombi.Core/Ombi.Core.csproj b/src/Ombi.Core/Ombi.Core.csproj index c2aeb1fd0..10e07822a 100644 --- a/src/Ombi.Core/Ombi.Core.csproj +++ b/src/Ombi.Core/Ombi.Core.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj index 6fe083fe3..028c37b43 100644 --- a/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj +++ b/src/Ombi.DependencyInjection/Ombi.DependencyInjection.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/src/Ombi.Helpers/Ombi.Helpers.csproj b/src/Ombi.Helpers/Ombi.Helpers.csproj index e94afc816..2aaaa076f 100644 --- a/src/Ombi.Helpers/Ombi.Helpers.csproj +++ b/src/Ombi.Helpers/Ombi.Helpers.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj b/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj index a124f01bd..ea1d17f8c 100644 --- a/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj +++ b/src/Ombi.Schedule.Tests/Ombi.Schedule.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Ombi.Store/Ombi.Store.csproj b/src/Ombi.Store/Ombi.Store.csproj index 2ceb78424..cdbd3fe84 100644 --- a/src/Ombi.Store/Ombi.Store.csproj +++ b/src/Ombi.Store/Ombi.Store.csproj @@ -10,10 +10,10 @@ - - - - + + + + From 3bcaefc24a6b4d20dcf193d71e129ec9b5ef0319 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 18 Sep 2018 12:39:44 +0100 Subject: [PATCH 443/495] Fixed #2516 --- src/Ombi/ClientApp/app/search/music/albumsearch.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html index fd5f71075..d2430bd62 100644 --- a/src/Ombi/ClientApp/app/search/music/albumsearch.component.html +++ b/src/Ombi/ClientApp/app/search/music/albumsearch.component.html @@ -13,7 +13,7 @@

    - {{result.title}} + {{result.title | truncate: 36}}

    From 2e90edd9c3560c8d90983e3d381019c48d423353 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 18 Sep 2018 12:48:29 +0100 Subject: [PATCH 444/495] Fixed #2485 --- .../ClientApp/app/login/login.component.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Ombi/ClientApp/app/login/login.component.ts b/src/Ombi/ClientApp/app/login/login.component.ts index abf387fa1..0c04edbad 100644 --- a/src/Ombi/ClientApp/app/login/login.component.ts +++ b/src/Ombi/ClientApp/app/login/login.component.ts @@ -127,18 +127,18 @@ export class LoginComponent implements OnDestroy, OnInit { } public oauth() { + const oAuthWindow = window.open(window.location.toString(), "_blank", `toolbar=0, + location=0, + status=0, + menubar=0, + scrollbars=1, + resizable=1, + width=500, + height=500`); this.plexTv.GetPin(this.clientId, this.appName).subscribe((pin: any) => { this.authService.login({ usePlexOAuth: true, password: "", rememberMe: true, username: "", plexTvPin: pin }).subscribe(x => { - - window.open(x.url, "_blank", `toolbar=0, - location=0, - status=0, - menubar=0, - scrollbars=1, - resizable=1, - width=500, - height=500`); + oAuthWindow!.location.replace(x.url); this.pinTimer = setInterval(() => { this.notify.info("Authenticating", "Loading... Please Wait"); From 2886f6e6fa2f81bb2595b4228a7f6368448d71f2 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 18 Sep 2018 13:05:41 +0100 Subject: [PATCH 445/495] Consolodated the usermanagement stuff, still a !wip but it's a start --- .../usermanagement-add.component.html | 79 --------- .../usermanagement-add.component.ts | 70 -------- .../usermanagement-edit.component.html | 70 -------- .../usermanagement-user.component.html | 154 ++++++++++++++++++ ...nt.ts => usermanagement-user.component.ts} | 90 ++++++++-- .../usermanagement.component.html | 4 +- .../usermanagement/usermanagement.module.ts | 4 +- 7 files changed, 234 insertions(+), 237 deletions(-) delete mode 100644 src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.html delete mode 100644 src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts delete mode 100644 src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html create mode 100644 src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html rename src/Ombi/ClientApp/app/usermanagement/{usermanagement-edit.component.ts => usermanagement-user.component.ts} (51%) diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.html deleted file mode 100644 index 683bd5620..000000000 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.html +++ /dev/null @@ -1,79 +0,0 @@ - -

    Create User

    - - - -
    -
    - - -
    -
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts deleted file mode 100644 index 36b187e79..000000000 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Component, OnInit } from "@angular/core"; -import { Router } from "@angular/router"; - -import { ICheckbox, IUser, UserType } from "../interfaces"; -import { IdentityService, NotificationService } from "../services"; - -@Component({ - templateUrl: "./usermanagement-add.component.html", -}) -export class UserManagementAddComponent implements OnInit { - public user: IUser; - public availableClaims: ICheckbox[]; - public confirmPass: ""; - - constructor(private identityService: IdentityService, - private notificationSerivce: NotificationService, - private router: Router) { } - - public ngOnInit() { - this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); - this.user = { - alias: "", - claims: [], - emailAddress: "", - id: "", - password: "", - userName: "", - userType: UserType.LocalUser, - checked: false, - hasLoggedIn: false, - lastLoggedIn: new Date(), - episodeRequestLimit: 0, - movieRequestLimit: 0, - userAccessToken: "", - }; - } - - public create() { - this.user.claims = this.availableClaims; - - if (this.user.password) { - if (this.user.password !== this.confirmPass) { - this.notificationSerivce.error("Passwords do not match"); - return; - } - } - const hasClaims = this.availableClaims.some((item) => { - if (item.enabled) { return true; } - - return false; - }); - - if (!hasClaims) { - this.notificationSerivce.error("Please assign a role"); - return; - } - - this.identityService.createUser(this.user).subscribe(x => { - if (x.successful) { - this.notificationSerivce.success(`The user ${this.user.userName} has been created successfully`); - this.router.navigate(["usermanagement"]); - } else { - x.errors.forEach((val) => { - this.notificationSerivce.error(val); - }); - } - }); - } - -} diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html deleted file mode 100644 index 95a70cab0..000000000 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html +++ /dev/null @@ -1,70 +0,0 @@ -
    -
    -

    User: {{user.userName}}

    - - - - - - -
    - - - - -
    -
    -
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html new file mode 100644 index 000000000..0dcb24cb4 --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html @@ -0,0 +1,154 @@ +
    +

    Create User

    + + + +
    +
    + + +
    +
    +
    + +
    + +
    +
    +

    User: {{user.userName}}

    + + + + + + +
    + + + + +
    +
    +
    +
    \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.ts similarity index 51% rename from src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts rename to src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.ts index a83ef5a97..bca4f1058 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.ts @@ -1,32 +1,92 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; -import { ConfirmationService } from "primeng/primeng"; +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +import { ICheckbox, IUser, UserType } from "../interfaces"; +import { IdentityService, NotificationService } from "../services"; -import { ActivatedRoute } from "@angular/router"; -import { IUser } from "../interfaces"; -import { IdentityService } from "../services"; -import { NotificationService } from "../services"; +import { ConfirmationService } from "primeng/primeng"; @Component({ - templateUrl: "./usermanagement-edit.component.html", + templateUrl: "./usermanagement-user.component.html", }) -export class UserManagementEditComponent { +export class UserManagementUserComponent implements OnInit { + public user: IUser; public userId: string; + public availableClaims: ICheckbox[]; + public confirmPass: ""; + + public edit: boolean; constructor(private identityService: IdentityService, - private route: ActivatedRoute, private notificationService: NotificationService, private router: Router, + private route: ActivatedRoute, private confirmationService: ConfirmationService) { - this.route.params + this.route.params .subscribe((params: any) => { - this.userId = params.id; + if(params.id) { + this.userId = params.id; + this.edit = true; + this.identityService.getUserById(this.userId).subscribe(x => { + this.user = x; + }); + } + }); + + } + + public ngOnInit() { + this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); + if(!this.edit) { + this.user = { + alias: "", + claims: [], + emailAddress: "", + id: "", + password: "", + userName: "", + userType: UserType.LocalUser, + checked: false, + hasLoggedIn: false, + lastLoggedIn: new Date(), + episodeRequestLimit: 0, + movieRequestLimit: 0, + userAccessToken: "", + }; + } + } + + public create() { + this.user.claims = this.availableClaims; + + if (this.user.password) { + if (this.user.password !== this.confirmPass) { + this.notificationService.error("Passwords do not match"); + return; + } + } + const hasClaims = this.availableClaims.some((item) => { + if (item.enabled) { return true; } - this.identityService.getUserById(this.userId).subscribe(x => { - this.user = x; + return false; + }); + + if (!hasClaims) { + this.notificationService.error("Please assign a role"); + return; + } + + this.identityService.createUser(this.user).subscribe(x => { + if (x.successful) { + this.notificationService.success(`The user ${this.user.userName} has been created successfully`); + this.router.navigate(["usermanagement"]); + } else { + x.errors.forEach((val) => { + this.notificationService.error(val); }); - }); + } + }); } public delete() { diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index 519db023f..de78f64f8 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -2,7 +2,7 @@ - +
    @@ -94,7 +94,7 @@ Emby User - Details/Edit + Details/Edit diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts index cf25446f5..7ff799729 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts @@ -10,6 +10,7 @@ import { UpdateDetailsComponent } from "./updatedetails.component"; import { UserManagementAddComponent } from "./usermanagement-add.component"; import { UserManagementEditComponent } from "./usermanagement-edit.component"; import { UserManagementComponent } from "./usermanagement.component"; +import { UserManagementUserComponent } from "./usermanagement-user.component"; import { PipeModule } from "../pipes/pipe.module"; import { IdentityService, PlexService } from "../services"; @@ -22,7 +23,7 @@ import { AddPlexUserComponent } from "./addplexuser.component"; const routes: Routes = [ { path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, { path: "add", component: UserManagementAddComponent, canActivate: [AuthGuard] }, - { path: "edit/:id", component: UserManagementEditComponent, canActivate: [AuthGuard] }, + { path: "user", component: UserManagementUserComponent, canActivate: [AuthGuard] }, { path: "updatedetails", component: UpdateDetailsComponent, canActivate: [AuthGuard] }, ]; @@ -46,6 +47,7 @@ const routes: Routes = [ UserManagementEditComponent, UpdateDetailsComponent, AddPlexUserComponent, + UserManagementUserComponent, ], entryComponents:[ AddPlexUserComponent, From de2e3abfe02c70fbe1a61d51b2409ed189138f67 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 18 Sep 2018 14:15:26 +0100 Subject: [PATCH 446/495] Added the API to add user notification preferences --- src/Ombi.Store/Entities/OmbiUser.cs | 1 + .../Entities/UserNotificationPreferences.cs | 7 ++- src/Ombi/Controllers/IdentityController.cs | 50 +++++++++++++++++-- .../Identity/AddNotificationPreference.cs | 12 +++++ 4 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 src/Ombi/Models/Identity/AddNotificationPreference.cs diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index 9513df818..801a50cb1 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -28,6 +28,7 @@ namespace Ombi.Store.Entities public string UserAccessToken { get; set; } public List NotificationUserIds { get; set; } + public List UserNotificationPreferences { get; set; } [NotMapped] public bool IsEmbyConnect => UserType == UserType.EmbyUser && EmbyConnectUserId.HasValue(); diff --git a/src/Ombi.Store/Entities/UserNotificationPreferences.cs b/src/Ombi.Store/Entities/UserNotificationPreferences.cs index c779480c8..7196d38ca 100644 --- a/src/Ombi.Store/Entities/UserNotificationPreferences.cs +++ b/src/Ombi.Store/Entities/UserNotificationPreferences.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; -using System.Text; +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; using Ombi.Helpers; namespace Ombi.Store.Entities @@ -15,6 +13,7 @@ namespace Ombi.Store.Entities public string Value { get; set; } [ForeignKey(nameof(UserId))] + [JsonIgnore] public OmbiUser User { get; set; } } } diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index d7e556f26..71cac7a76 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -793,15 +793,14 @@ namespace Ombi.Controllers [HttpGet("notificationpreferences")] public async Task> GetUserPreferences() { - //TODO potentially use a view model var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); var userPreferences = await _userNotificationPreferences.GetAll().Where(x => x.UserId == user.Id).ToListAsync(); var agents = Enum.GetValues(typeof(NotificationAgent)).Cast(); foreach (var a in agents) { - var hasAgent = userPreferences.Any(x => x.Agent == a); - if (!hasAgent) + var agent = userPreferences.FirstOrDefault(x => x.Agent == a); + if (agent == null) { // Create the default userPreferences.Add(new UserNotificationPreferences @@ -809,11 +808,56 @@ namespace Ombi.Controllers Agent = a, }); } + else + { + userPreferences.Add(agent); + } } return userPreferences; } + [HttpPost("NotificationPreferences")] + public async Task AddUserNotificationPreference([FromBody] AddNotificationPreference pref) + { + // Make sure the user exists + var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == pref.UserId); + if (user == null) + { + return NotFound(); + } + // Check if we are editing a different user than ourself, if we are then we need to power user role + var me = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); + if (!me.Id.Equals(user.Id, StringComparison.InvariantCultureIgnoreCase)) + { + var isPowerUser = await UserManager.IsInRoleAsync(me, OmbiRoles.PowerUser); + var isAdmin = await UserManager.IsInRoleAsync(me, OmbiRoles.Admin); + if (!isPowerUser && !isAdmin) + { + return Unauthorized(); + } + } + + // Make sure we don't already have a preference for this agent + var existingPreference = await _userNotificationPreferences.GetAll() + .FirstOrDefaultAsync(x => x.UserId == user.Id && x.Agent == pref.Agent); + if (existingPreference != null) + { + // Update it + existingPreference.Value = pref.Value; + existingPreference.Enabled = pref.Enabled; + } + await _userNotificationPreferences.Add(new UserNotificationPreferences + { + Agent = pref.Agent, + Enabled = pref.Enabled, + UserId = pref.UserId, + Value = pref.Value + }); + + return Json(true); + } + private async Task> AddRoles(IEnumerable roles, OmbiUser ombiUser) { var roleResult = new List(); diff --git a/src/Ombi/Models/Identity/AddNotificationPreference.cs b/src/Ombi/Models/Identity/AddNotificationPreference.cs new file mode 100644 index 000000000..51dc7f6fe --- /dev/null +++ b/src/Ombi/Models/Identity/AddNotificationPreference.cs @@ -0,0 +1,12 @@ +using Ombi.Helpers; + +namespace Ombi.Models.Identity +{ + public class AddNotificationPreference + { + public NotificationAgent Agent { get; set; } + public string UserId { get; set; } + public string Value { get; set; } + public bool Enabled { get; set; } + } +} \ No newline at end of file From 8573b7c729aba788bd575cdbf994d33ff8fed53e Mon Sep 17 00:00:00 2001 From: TidusJar Date: Tue, 18 Sep 2018 15:51:22 +0100 Subject: [PATCH 447/495] Did the notification side of things with the custom user defined preference !wip --- .../Agents/DiscordNotification.cs | 5 +++-- .../Agents/EmailNotification.cs | 3 ++- .../Agents/MattermostNotification.cs | 3 ++- .../Agents/MobileNotification.cs | 3 ++- .../Agents/PushbulletNotification.cs | 3 ++- .../Agents/PushoverNotification.cs | 3 ++- .../Agents/SlackNotification.cs | 3 ++- .../Agents/TelegramNotification.cs | 3 ++- src/Ombi.Notifications/BaseNotification.cs | 22 ++++++++++++++----- .../NotificationMessageCurlys.cs | 10 ++++++--- .../usermanagement/usermanagement.module.ts | 3 --- src/Ombi/Ombi.csproj | 2 +- 12 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/Ombi.Notifications/Agents/DiscordNotification.cs b/src/Ombi.Notifications/Agents/DiscordNotification.cs index d788b471c..84e907053 100644 --- a/src/Ombi.Notifications/Agents/DiscordNotification.cs +++ b/src/Ombi.Notifications/Agents/DiscordNotification.cs @@ -20,8 +20,9 @@ namespace Ombi.Notifications.Agents { public DiscordNotification(IDiscordApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, - IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub, IMusicRequestRepository music) - : base(sn, r, m, t, s, log, sub, music) + IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) + : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/EmailNotification.cs b/src/Ombi.Notifications/Agents/EmailNotification.cs index 3ab045e87..cff1f3b80 100644 --- a/src/Ombi.Notifications/Agents/EmailNotification.cs +++ b/src/Ombi.Notifications/Agents/EmailNotification.cs @@ -22,7 +22,8 @@ namespace Ombi.Notifications.Agents public class EmailNotification : BaseNotification, IEmailNotification { public EmailNotification(ISettingsService settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService c, - ILogger log, UserManager um, IRepository sub, IMusicRequestRepository music) : base(settings, r, m, t, c, log, sub, music) + ILogger log, UserManager um, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(settings, r, m, t, c, log, sub, music, userPref) { EmailProvider = prov; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MattermostNotification.cs b/src/Ombi.Notifications/Agents/MattermostNotification.cs index 9e8a34e3b..37e597854 100644 --- a/src/Ombi.Notifications/Agents/MattermostNotification.cs +++ b/src/Ombi.Notifications/Agents/MattermostNotification.cs @@ -21,7 +21,8 @@ namespace Ombi.Notifications.Agents public class MattermostNotification : BaseNotification, IMattermostNotification { public MattermostNotification(IMattermostApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MobileNotification.cs b/src/Ombi.Notifications/Agents/MobileNotification.cs index c521b99a4..a16785909 100644 --- a/src/Ombi.Notifications/Agents/MobileNotification.cs +++ b/src/Ombi.Notifications/Agents/MobileNotification.cs @@ -22,7 +22,8 @@ namespace Ombi.Notifications.Agents { public MobileNotification(IOneSignalApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository notification, - UserManager um, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + UserManager um, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { _api = api; _logger = log; diff --git a/src/Ombi.Notifications/Agents/PushbulletNotification.cs b/src/Ombi.Notifications/Agents/PushbulletNotification.cs index 0e488bf79..6c6b1f789 100644 --- a/src/Ombi.Notifications/Agents/PushbulletNotification.cs +++ b/src/Ombi.Notifications/Agents/PushbulletNotification.cs @@ -17,7 +17,8 @@ namespace Ombi.Notifications.Agents public class PushbulletNotification : BaseNotification, IPushbulletNotification { public PushbulletNotification(IPushbulletApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/PushoverNotification.cs b/src/Ombi.Notifications/Agents/PushoverNotification.cs index b5d743cc2..86f91dbaa 100644 --- a/src/Ombi.Notifications/Agents/PushoverNotification.cs +++ b/src/Ombi.Notifications/Agents/PushoverNotification.cs @@ -18,7 +18,8 @@ namespace Ombi.Notifications.Agents public class PushoverNotification : BaseNotification, IPushoverNotification { public PushoverNotification(IPushoverApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/SlackNotification.cs b/src/Ombi.Notifications/Agents/SlackNotification.cs index 6c04f5ea6..ee81e9729 100644 --- a/src/Ombi.Notifications/Agents/SlackNotification.cs +++ b/src/Ombi.Notifications/Agents/SlackNotification.cs @@ -18,7 +18,8 @@ namespace Ombi.Notifications.Agents public class SlackNotification : BaseNotification, ISlackNotification { public SlackNotification(ISlackApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/TelegramNotification.cs b/src/Ombi.Notifications/Agents/TelegramNotification.cs index 7bcda7c7f..cf463bf99 100644 --- a/src/Ombi.Notifications/Agents/TelegramNotification.cs +++ b/src/Ombi.Notifications/Agents/TelegramNotification.cs @@ -19,7 +19,8 @@ namespace Ombi.Notifications.Agents public TelegramNotification(ITelegramApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s - , IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t,s,log, sub, music) + , IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t,s,log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/BaseNotification.cs index 287f86455..d351c8283 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/BaseNotification.cs @@ -19,7 +19,8 @@ namespace Ombi.Notifications.Interfaces public abstract class BaseNotification : INotification where T : Settings.Settings.Models.Settings, new() { protected BaseNotification(ISettingsService settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv, - ISettingsService customization, ILogger> log, IRepository sub, IMusicRequestRepository album) + ISettingsService customization, ILogger> log, IRepository sub, IMusicRequestRepository album, + IRepository notificationUserPreferences) { Settings = settings; TemplateRepository = templateRepo; @@ -31,6 +32,7 @@ namespace Ombi.Notifications.Interfaces RequestSubscription = sub; _log = log; AlbumRepository = album; + UserNotificationPreferences = notificationUserPreferences; } protected ISettingsService Settings { get; } @@ -40,6 +42,7 @@ namespace Ombi.Notifications.Interfaces protected IMusicRequestRepository AlbumRepository { get; } protected CustomizationSettings Customization { get; set; } protected IRepository RequestSubscription { get; set; } + protected IRepository UserNotificationPreferences { get; set; } private ISettingsService CustomizationSettings { get; } private readonly ILogger> _log; @@ -167,7 +170,7 @@ namespace Ombi.Notifications.Interfaces { return new NotificationMessageContent { Disabled = true }; } - var parsed = Parse(model, template); + var parsed = Parse(model, template, agent); return parsed; } @@ -178,25 +181,32 @@ namespace Ombi.Notifications.Interfaces return subs.Select(x => x.User); } - private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template) + protected UserNotificationPreferences GetUserPreference(string userId, NotificationAgent agent) + { + return UserNotificationPreferences.GetAll() + .FirstOrDefault(x => x.Enabled && x.Agent == agent && x.UserId == userId); + } + + private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template, NotificationAgent agent) { var resolver = new NotificationMessageResolver(); var curlys = new NotificationMessageCurlys(); + var preference = GetUserPreference(model.UserId, agent); if (model.RequestType == RequestType.Movie) { _log.LogDebug("Notification options: {@model}, Req: {@MovieRequest}, Settings: {@Customization}", model, MovieRequest, Customization); - curlys.Setup(model, MovieRequest, Customization); + curlys.Setup(model, MovieRequest, Customization, preference); } else if (model.RequestType == RequestType.TvShow) { _log.LogDebug("Notification options: {@model}, Req: {@TvRequest}, Settings: {@Customization}", model, TvRequest, Customization); - curlys.Setup(model, TvRequest, Customization); + curlys.Setup(model, TvRequest, Customization, preference); } else if (model.RequestType == RequestType.Album) { _log.LogDebug("Notification options: {@model}, Req: {@AlbumRequest}, Settings: {@Customization}", model, AlbumRequest, Customization); - curlys.Setup(model, AlbumRequest, Customization); + curlys.Setup(model, AlbumRequest, Customization, preference); } var parsed = resolver.ParseMessage(template, curlys); diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 710f64619..5bc74f68e 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -14,9 +14,10 @@ namespace Ombi.Notifications { public class NotificationMessageCurlys { - public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s) + public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s, UserNotificationPreferences pref) { LoadIssues(opts); + UserPreference = pref.Enabled ? pref.Value : string.Empty; string title; if (req == null) { @@ -58,9 +59,10 @@ namespace Ombi.Notifications AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; } - public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s) + public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s, UserNotificationPreferences pref) { LoadIssues(opts); + UserPreference = pref.Enabled ? pref.Value : string.Empty; string title; if (req == null) { @@ -101,9 +103,10 @@ namespace Ombi.Notifications Alias = username.Alias.HasValue() ? username.Alias : username.UserName; } - public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s) + public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, UserNotificationPreferences pref) { LoadIssues(opts); + UserPreference = pref.Enabled ? pref.Value : string.Empty; string title; if (req == null) { @@ -221,6 +224,7 @@ namespace Ombi.Notifications public string IssueStatus { get; set; } public string IssueSubject { get; set; } public string NewIssueComment { get; set; } + public string UserPreference { get; set; } // System Defined private string LongDate => DateTime.Now.ToString("D"); diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts index 7ff799729..81101978e 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts @@ -7,8 +7,6 @@ import { ConfirmationService, ConfirmDialogModule, MultiSelectModule, SidebarMod import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { UpdateDetailsComponent } from "./updatedetails.component"; -import { UserManagementAddComponent } from "./usermanagement-add.component"; -import { UserManagementEditComponent } from "./usermanagement-edit.component"; import { UserManagementComponent } from "./usermanagement.component"; import { UserManagementUserComponent } from "./usermanagement-user.component"; @@ -22,7 +20,6 @@ import { AddPlexUserComponent } from "./addplexuser.component"; const routes: Routes = [ { path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, - { path: "add", component: UserManagementAddComponent, canActivate: [AuthGuard] }, { path: "user", component: UserManagementUserComponent, canActivate: [AuthGuard] }, { path: "updatedetails", component: UpdateDetailsComponent, canActivate: [AuthGuard] }, ]; diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index 6640a8eb8..1ca47d306 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -8,7 +8,7 @@ $(SemVer) $(FullVer) - 2.8 + 3.0 false From 2204559b0aee9dfc1ca50ff9336d1e3fa9957001 Mon Sep 17 00:00:00 2001 From: TidusJar Date: Wed, 19 Sep 2018 13:38:41 +0100 Subject: [PATCH 448/495] Added the Notification Preferences to the user --- ...24_UserNotificationPreferences.Designer.cs | 1118 +++++++++++++++++ ...80919073124_UserNotificationPreferences.cs | 43 + .../Migrations/OmbiContextModelSnapshot.cs | 29 +- src/Ombi/ClientApp/app/interfaces/IUser.ts | 19 + .../app/services/identity.service.ts | 14 +- .../usermanagement-user.component.html | 255 ++-- .../usermanagement-user.component.ts | 12 +- .../usermanagement/usermanagement.module.ts | 3 +- src/Ombi/Controllers/IdentityController.cs | 98 +- 9 files changed, 1416 insertions(+), 175 deletions(-) create mode 100644 src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs create mode 100644 src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs diff --git a/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs new file mode 100644 index 000000000..d61ea31ba --- /dev/null +++ b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs @@ -0,0 +1,1118 @@ +// +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 +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180919073124_UserNotificationPreferences")] + partial class UserNotificationPreferences + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + 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.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + 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.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + 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.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + 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.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + 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.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + 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"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs new file mode 100644 index 000000000..adb062af7 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class UserNotificationPreferences : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserNotificationPreferences", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: true), + Agent = table.Column(nullable: false), + Enabled = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserNotificationPreferences", x => x.Id); + table.ForeignKey( + name: "FK_UserNotificationPreferences_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserNotificationPreferences_UserId", + table: "UserNotificationPreferences", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserNotificationPreferences"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 64400e58c..5e5aa23c6 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace Ombi.Store.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -870,6 +870,26 @@ namespace Ombi.Store.Migrations b.ToTable("Tokens"); }); + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => { b.Property("Id") @@ -1068,6 +1088,13 @@ namespace Ombi.Store.Migrations .HasForeignKey("UserId"); }); + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => { b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index cd96848fb..04546abf9 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -66,3 +66,22 @@ export interface IMassEmailModel { body: string; users: IUser[]; } + +export interface INotificationPreferences { + id: number; + userId: string; + agent: INotificationAgent; + enabled: boolean; + value: string; +} + +export enum INotificationAgent { + Email = 0, + Discord = 1, + Pushbullet = 2, + Pushover = 3, + Telegram = 4, + Slack = 5, + Mattermost = 6, + Mobile = 7, +} diff --git a/src/Ombi/ClientApp/app/services/identity.service.ts b/src/Ombi/ClientApp/app/services/identity.service.ts index e3dc1d8ad..bce159ebe 100644 --- a/src/Ombi/ClientApp/app/services/identity.service.ts +++ b/src/Ombi/ClientApp/app/services/identity.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { ICheckbox, ICreateWizardUser, IIdentityResult, IResetPasswordToken, IUpdateLocalUser, IUser, IWizardUserResult } from "../interfaces"; +import { ICheckbox, ICreateWizardUser, IIdentityResult, INotificationPreferences, IResetPasswordToken, IUpdateLocalUser, IUser, IWizardUserResult } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -43,6 +43,11 @@ export class IdentityService extends ServiceHelpers { public updateUser(user: IUser): Observable { return this.http.put(this.url, JSON.stringify(user), {headers: this.headers}); } + + public updateNotificationPreferences(pref: INotificationPreferences[]): Observable { + return this.http.post(`${this.url}NotificationPreferences`, JSON.stringify(pref), {headers: this.headers}); + } + public updateLocalUser(user: IUpdateLocalUser): Observable { return this.http.put(this.url + "local", JSON.stringify(user), {headers: this.headers}); } @@ -67,6 +72,13 @@ export class IdentityService extends ServiceHelpers { return this.http.post(`${this.url}welcomeEmail`, JSON.stringify(user), {headers: this.headers}); } + public getNotificationPreferences(): Observable { + return this.http.get(`${this.url}notificationpreferences`, {headers: this.headers}); + } + public getNotificationPreferencesForUser(userId: string): Observable { + return this.http.get(`${this.url}notificationpreferences/${userId}`, {headers: this.headers}); + } + public hasRole(role: string): boolean { const roles = localStorage.getItem("roles") as string[] | null; if (roles) { diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html index 0dcb24cb4..da1b4076e 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html @@ -1,153 +1,150 @@ -
    -

    Create User

    +

    Create User

    +

    User: {{user.userName}}

    +
    -