Merge pull request #7 from tidusjar/develop

Bringing develop up to date
pull/2197/head
Anojh Thayaparan 7 years ago committed by GitHub
commit ccb496f252
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -9,7 +9,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -1,6 +1,8 @@
using System.Threading.Tasks; using System;
using System.Threading.Tasks;
using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Friends; using Ombi.Api.Plex.Models.Friends;
using Ombi.Api.Plex.Models.OAuth;
using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status; using Ombi.Api.Plex.Models.Status;
@ -20,5 +22,8 @@ namespace Ombi.Api.Plex
Task<PlexFriends> GetUsers(string authToken); Task<PlexFriends> GetUsers(string authToken);
Task<PlexAccount> GetAccount(string authToken); Task<PlexAccount> GetAccount(string authToken);
Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId); Task<PlexMetadata> GetRecentlyAdded(string authToken, string uri, string sectionId);
Task<OAuthPin> CreatePin();
Task<OAuthPin> GetPin(int pinId);
Uri GetOAuthUrl(int pinId, string code, string applicationUrl, bool wizard);
} }
} }

@ -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; }
}
}

@ -1,20 +1,53 @@
using System.Net.Http; using System;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Friends; using Ombi.Api.Plex.Models.Friends;
using Ombi.Api.Plex.Models.OAuth;
using Ombi.Api.Plex.Models.Server; using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status; 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 namespace Ombi.Api.Plex
{ {
public class PlexApi : IPlexApi public class PlexApi : IPlexApi
{ {
public PlexApi(IApi api) public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings)
{ {
Api = api; Api = api;
_custom = settings;
} }
private IApi Api { get; } private IApi Api { get; }
private readonly ISettingsService<CustomizationSettings> _custom;
private string _app;
private string ApplicationName
{
get
{
if (string.IsNullOrEmpty(_app))
{
var settings = _custom.GetSettings();
if (settings.ApplicationName.IsNullOrEmpty())
{
_app = "Ombi";
}
else
{
_app = settings.ApplicationName;
}
return _app;
}
return _app;
}
}
private const string SignInUri = "https://plex.tv/users/sign_in.json"; private const string SignInUri = "https://plex.tv/users/sign_in.json";
private const string FriendsUri = "https://plex.tv/pms/friends/all"; private const string FriendsUri = "https://plex.tv/pms/friends/all";
@ -156,6 +189,50 @@ namespace Ombi.Api.Plex
return await Api.Request<PlexMetadata>(request); return await Api.Request<PlexMetadata>(request);
} }
public async Task<OAuthPin> CreatePin()
{
var request = new Request($"api/v2/pins", "https://plex.tv/", HttpMethod.Post);
request.AddQueryString("strong", "true");
AddHeaders(request);
return await Api.Request<OAuthPin>(request);
}
public async Task<OAuthPin> GetPin(int pinId)
{
var request = new Request($"api/v2/pins/{pinId}", "https://plex.tv/", HttpMethod.Get);
AddHeaders(request);
return await Api.Request<OAuthPin>(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($"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]", "Ombi");
request.AddQueryString("context[device][environment]", "bundled");
request.AddQueryString("clientID", $"OmbiV3");
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;
}
/// <summary> /// <summary>
/// Adds the required headers and also the authorization header /// Adds the required headers and also the authorization header
/// </summary> /// </summary>
@ -174,7 +251,7 @@ namespace Ombi.Api.Plex
private void AddHeaders(Request request) private void AddHeaders(Request request)
{ {
request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3"); request.AddHeader("X-Plex-Client-Identifier", $"OmbiV3");
request.AddHeader("X-Plex-Product", "Ombi"); request.AddHeader("X-Plex-Product", ApplicationName);
request.AddHeader("X-Plex-Version", "3"); request.AddHeader("X-Plex-Version", "3");
request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml"); request.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
request.AddHeader("Accept", "application/json"); request.AddHeader("Accept", "application/json");

@ -1,4 +1,5 @@
using System; using System;
using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Ombi.Api.Pushover.Models; using Ombi.Api.Pushover.Models;
@ -17,7 +18,7 @@ namespace Ombi.Api.Pushover
public async Task<PushoverResponse> PushAsync(string accessToken, string message, string userToken) public async Task<PushoverResponse> 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<PushoverResponse>(request); var result = await _api.Request<PushoverResponse>(request);
return result; return result;

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Polly" Version="5.8.0" /> <PackageReference Include="Polly" Version="5.8.0" />
<PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup> </ItemGroup>

@ -105,10 +105,10 @@ namespace Ombi.Api
hasQuery = true; hasQuery = true;
startingTag = builder.Query.Contains("?") ? "&" : "?"; startingTag = builder.Query.Contains("?") ? "&" : "?";
} }
builder.Query = hasQuery builder.Query = hasQuery
? $"{builder.Query}{startingTag}{key}={value}" ? $"{builder.Query}{startingTag}{key}={value}"
: $"{startingTag}{key}={value}"; : $"{startingTag}{key}={value}";
_modified = builder.Uri; _modified = builder.Uri;
} }

@ -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<CustomizationSettings> settings)
{
_api = api;
_customizationSettingsService = settings;
}
private readonly IPlexApi _api;
private readonly ISettingsService<CustomizationSettings> _customizationSettingsService;
public async Task<OAuthPin> RequestPin()
{
var pin = await _api.CreatePin();
return pin;
}
public async Task<string> 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<PlexAccount> GetAccount(string accessToken)
{
return await _api.GetAccount(accessToken);
}
public async Task<Uri> 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;
}
}
}

@ -32,14 +32,7 @@ namespace Ombi.Core.Engine.Interfaces
private OmbiUser _user; private OmbiUser _user;
protected async Task<OmbiUser> GetUser() protected async Task<OmbiUser> GetUser()
{ {
if (IsApiUser) return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName.Equals(Username, StringComparison.CurrentCultureIgnoreCase)));
{
return new OmbiUser
{
UserName = Username,
};
}
return _user ?? (_user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == Username));
} }
protected async Task<string> UserAlias() protected async Task<string> UserAlias()
@ -49,10 +42,6 @@ namespace Ombi.Core.Engine.Interfaces
protected async Task<bool> IsInRole(string roleName) protected async Task<bool> IsInRole(string roleName)
{ {
if (IsApiUser && roleName != OmbiRoles.Disabled)
{
return true;
}
return await UserManager.IsInRoleAsync(await GetUser(), roleName); return await UserManager.IsInRoleAsync(await GetUser(), roleName);
} }
@ -72,7 +61,5 @@ namespace Ombi.Core.Engine.Interfaces
var ruleResults = await Rules.StartSpecificRules(model, rule); var ruleResults = await Rules.StartSpecificRules(model, rule);
return ruleResults; return ruleResults;
} }
private bool IsApiUser => Username.Equals("Api", StringComparison.CurrentCultureIgnoreCase);
} }
} }

@ -63,13 +63,17 @@ namespace Ombi.Core.Engine
var recentlyAddedLog = new HashSet<RecentlyAddedLog>(); var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
foreach (var p in plexContent) foreach (var p in plexContent)
{ {
if (!p.HasTheMovieDb)
{
continue;
}
if (p.Type == PlexMediaTypeEntity.Movie) if (p.Type == PlexMediaTypeEntity.Movie)
{ {
recentlyAddedLog.Add(new RecentlyAddedLog recentlyAddedLog.Add(new RecentlyAddedLog
{ {
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex, Type = RecentlyAddedType.Plex,
ContentId = p.Id, ContentId = int.Parse(p.TheMovieDbId),
ContentType = ContentType.Parent ContentType = ContentType.Parent
}); });
} }
@ -78,12 +82,18 @@ namespace Ombi.Core.Engine
// Add the episodes // Add the episodes
foreach (var ep in p.Episodes) foreach (var ep in p.Episodes)
{ {
if (!ep.Series.HasTvDb)
{
continue;
}
recentlyAddedLog.Add(new RecentlyAddedLog recentlyAddedLog.Add(new RecentlyAddedLog
{ {
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex, Type = RecentlyAddedType.Plex,
ContentId = ep.Id, ContentId = int.Parse(ep.Series.TvDbId),
ContentType = ContentType.Episode ContentType = ContentType.Episode,
EpisodeNumber = ep.EpisodeNumber,
SeasonNumber = ep.SeasonNumber
}); });
} }
} }
@ -91,13 +101,17 @@ namespace Ombi.Core.Engine
foreach (var e in embyContent) foreach (var e in embyContent)
{ {
if (e.TheMovieDbId.IsNullOrEmpty())
{
continue;
}
if (e.Type == EmbyMediaType.Movie) if (e.Type == EmbyMediaType.Movie)
{ {
recentlyAddedLog.Add(new RecentlyAddedLog recentlyAddedLog.Add(new RecentlyAddedLog
{ {
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby, Type = RecentlyAddedType.Emby,
ContentId = e.Id, ContentId = int.Parse(e.TheMovieDbId),
ContentType = ContentType.Parent ContentType = ContentType.Parent
}); });
} }
@ -106,12 +120,18 @@ namespace Ombi.Core.Engine
// Add the episodes // Add the episodes
foreach (var ep in e.Episodes) foreach (var ep in e.Episodes)
{ {
if (ep.Series.TvDbId.IsNullOrEmpty())
{
continue;
}
recentlyAddedLog.Add(new RecentlyAddedLog recentlyAddedLog.Add(new RecentlyAddedLog
{ {
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby, Type = RecentlyAddedType.Emby,
ContentId = ep.Id, ContentId = int.Parse(ep.Series.TvDbId),
ContentType = ContentType.Episode ContentType = ContentType.Episode,
EpisodeNumber = ep.EpisodeNumber,
SeasonNumber = ep.SeasonNumber
}); });
} }
} }

@ -277,6 +277,7 @@ namespace Ombi.Core.Engine
results.ImdbId = request.ImdbId; results.ImdbId = request.ImdbId;
results.Overview = request.Overview; results.Overview = request.Overview;
results.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath); results.PosterPath = PosterPathHelper.FixPosterPath(request.PosterPath);
results.Background = PosterPathHelper.FixBackgroundPath(request.Background);
results.QualityOverride = request.QualityOverride; results.QualityOverride = request.QualityOverride;
results.RootFolder = request.RootFolder; results.RootFolder = request.RootFolder;

@ -30,6 +30,7 @@ namespace Ombi.Core.Helpers
public ChildRequests ChildRequest { get; set; } public ChildRequests ChildRequest { get; set; }
public List<SeasonsViewModel> TvRequests { get; protected set; } public List<SeasonsViewModel> TvRequests { get; protected set; }
public string PosterPath { get; protected set; } public string PosterPath { get; protected set; }
public string BackdropPath { get; protected set; }
public DateTime FirstAir { get; protected set; } public DateTime FirstAir { get; protected set; }
public TvRequests NewRequest { get; protected set; } public TvRequests NewRequest { get; protected set; }
protected TvMazeShow ShowInfo { get; set; } protected TvMazeShow ShowInfo { get; set; }
@ -44,6 +45,7 @@ namespace Ombi.Core.Helpers
{ {
var showIds = await MovieDbApi.GetTvExternals(result.Id); var showIds = await MovieDbApi.GetTvExternals(result.Id);
ShowInfo.externals.imdb = showIds.imdb_id; ShowInfo.externals.imdb = showIds.imdb_id;
BackdropPath = result.BackdropPath;
break; break;
} }
} }
@ -240,7 +242,8 @@ namespace Ombi.Core.Helpers
ImdbId = ShowInfo.externals?.imdb ?? string.Empty, ImdbId = ShowInfo.externals?.imdb ?? string.Empty,
TvDbId = tv.TvDbId, TvDbId = tv.TvDbId,
ChildRequests = new List<ChildRequests>(), ChildRequests = new List<ChildRequests>(),
TotalSeasons = tv.Seasons.Count() TotalSeasons = tv.Seasons.Count(),
Background = BackdropPath
}; };
NewRequest.ChildRequests.Add(ChildRequest); NewRequest.ChildRequests.Add(ChildRequest);

@ -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<string> GetAccessTokenFromPin(int pinId);
Task<OAuthPin> RequestPin();
Task<Uri> GetOAuthUrl(int pinId, string code);
Uri GetWizardOAuthUrl(int pinId, string code, string websiteAddress);
Task<PlexAccount> GetAccount(string accessToken);
}
}

@ -10,6 +10,7 @@ namespace Ombi.Core.Models.Search
public bool Requested { get; set; } public bool Requested { get; set; }
public bool Available { get; set; } public bool Available { get; set; }
public string PlexUrl { get; set; } public string PlexUrl { get; set; }
public string EmbyUrl { get; set; }
public string Quality { get; set; } public string Quality { get; set; }
public abstract RequestType Type { get; } public abstract RequestType Type { get; }

@ -11,12 +11,12 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" /> <PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.0.1" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="3.0.1" />
<PackageReference Include="Hangfire" Version="1.6.17" /> <PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.5" />
<PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" /> <PackageReference Include="MiniProfiler.AspNetCore" Version="4.0.0-alpha6-79" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="System.Diagnostics.Process" Version="4.3.0" /> <PackageReference Include="System.Diagnostics.Process" Version="4.3.0" />
</ItemGroup> </ItemGroup>

@ -44,6 +44,7 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null) if (item != null)
{ {
obj.Available = true; obj.Available = true;
obj.EmbyUrl = item.Url;
if (obj.Type == RequestType.TvShow) if (obj.Type == RequestType.TvShow)
{ {

@ -51,6 +51,7 @@ using Ombi.Store.Repository.Requests;
using Ombi.Updater; using Ombi.Updater;
using PlexContentCacher = Ombi.Schedule.Jobs.Plex; using PlexContentCacher = Ombi.Schedule.Jobs.Plex;
using Ombi.Api.Telegram; using Ombi.Api.Telegram;
using Ombi.Core.Authentication;
using Ombi.Core.Processor; using Ombi.Core.Processor;
using Ombi.Schedule.Jobs.Plex.Interfaces; using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.SickRage; using Ombi.Schedule.Jobs.SickRage;
@ -82,6 +83,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>(); services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>();
services.AddTransient<ITvSender, TvSender>(); services.AddTransient<ITvSender, TvSender>();
services.AddTransient<IMassEmailSender, MassEmailSender>(); services.AddTransient<IMassEmailSender, MassEmailSender>();
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
} }
public static void RegisterHttp(this IServiceCollection services) public static void RegisterHttp(this IServiceCollection services)
{ {

@ -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;
}
}
}

@ -14,5 +14,12 @@ namespace Ombi.Helpers
yield return source1; yield return source1;
} }
} }
public static HashSet<T> ToHashSet<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(source, comparer);
}
} }
} }

@ -12,7 +12,7 @@
<PackageReference Include="EasyCrypto" Version="3.3.2" /> <PackageReference Include="EasyCrypto" Version="3.3.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" /> <PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
<PackageReference Include="System.Security.Claims" Version="4.3.0" /> <PackageReference Include="System.Security.Claims" Version="4.3.0" />
</ItemGroup> </ItemGroup>

@ -18,5 +18,19 @@ namespace Ombi.Helpers
return poster; 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;
}
} }
} }

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Ensure.That" Version="7.0.0-pre32" /> <PackageReference Include="Ensure.That" Version="7.0.0-pre32" />
<PackageReference Include="MailKit" Version="1.20.0" /> <PackageReference Include="MailKit" Version="2.0.3" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -66,6 +66,8 @@ namespace Ombi.Schedule
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s)); RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s)); RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s)); RecurringJob.AddOrUpdate(() => _newsletter.Start(), JobSettingsHelper.Newsletter(s));
BackgroundJob.Enqueue(() => _refreshMetadata.Start());
} }

@ -114,6 +114,7 @@ namespace Ombi.Schedule.Jobs.Emby
Title = tvInfo.Name, Title = tvInfo.Name,
Type = EmbyMediaType.Series, Type = EmbyMediaType.Series,
EmbyId = tvShow.Id, EmbyId = tvShow.Id,
Url = EmbyHelper.GetEmbyMediaUrl(tvShow.Id),
AddedAt = DateTime.UtcNow AddedAt = DateTime.UtcNow
}); });
} }
@ -135,6 +136,7 @@ namespace Ombi.Schedule.Jobs.Emby
Title = movieInfo.Name, Title = movieInfo.Name,
Type = EmbyMediaType.Movie, Type = EmbyMediaType.Movie,
EmbyId = movieInfo.Id, EmbyId = movieInfo.Id,
Url = EmbyHelper.GetEmbyMediaUrl(movieInfo.Id),
AddedAt = DateTime.UtcNow, AddedAt = DateTime.UtcNow,
}); });
} }

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit; using MailKit;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb; using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models; using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze; using Ombi.Api.TvMaze;
@ -26,7 +29,7 @@ namespace Ombi.Schedule.Jobs.Ombi
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog, public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom, IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo, ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter) UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log)
{ {
_plex = plex; _plex = plex;
_emby = emby; _emby = emby;
@ -42,6 +45,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_emailSettings.ClearCache(); _emailSettings.ClearCache();
_customizationSettings.ClearCache(); _customizationSettings.ClearCache();
_newsletterSettings.ClearCache(); _newsletterSettings.ClearCache();
_log = log;
} }
private readonly IPlexContentRepository _plex; private readonly IPlexContentRepository _plex;
@ -55,6 +59,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ISettingsService<EmailNotificationSettings> _emailSettings; private readonly ISettingsService<EmailNotificationSettings> _emailSettings;
private readonly ISettingsService<NewsletterSettings> _newsletterSettings; private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
private readonly UserManager<OmbiUser> _userManager; private readonly UserManager<OmbiUser> _userManager;
private readonly ILogger _log;
public async Task Start(NewsletterSettings settings, bool test) public async Task Start(NewsletterSettings settings, bool test)
{ {
@ -74,8 +79,11 @@ namespace Ombi.Schedule.Jobs.Ombi
return; return;
} }
var customization = await _customizationSettings.GetSettingsAsync(); try
{
var customization = await _customizationSettings.GetSettingsAsync();
// Get the Content // Get the Content
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking(); var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking(); var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
@ -84,23 +92,31 @@ namespace Ombi.Schedule.Jobs.Ombi
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId); 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 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 addedPlexEpisodesLogIds =
var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).Select(x => x.ContentId); 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 // Filter out the ones that we haven't sent yet
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.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(x.Id)); var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(int.Parse(x.TheMovieDbId)));
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking(); _log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
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);
_log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count());
_log.LogInformation("Emby Episodes to send: {0}", embyEpisodesToSend.Count());
var body = string.Empty; var body = string.Empty;
if (test) if (test)
{ {
var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10); 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 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 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); var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
body = await BuildHtml(plexm, embym, plext, embyt, settings); body = await BuildHtml(plexm, embym, plext, embyt, settings);
} }
else else
@ -110,7 +126,6 @@ namespace Ombi.Schedule.Jobs.Ombi
{ {
return; return;
} }
} }
if (!test) if (!test)
@ -133,6 +148,7 @@ namespace Ombi.Schedule.Jobs.Ombi
var emailTasks = new List<Task>(); var emailTasks = new List<Task>();
foreach (var user in users) foreach (var user in users)
{ {
// Get the users to send it to
if (user.Email.IsNullOrEmpty()) if (user.Email.IsNullOrEmpty())
{ {
continue; continue;
@ -157,7 +173,7 @@ namespace Ombi.Schedule.Jobs.Ombi
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex, Type = RecentlyAddedType.Plex,
ContentType = ContentType.Parent, ContentType = ContentType.Parent,
ContentId = p.Id ContentId = int.Parse(p.TheMovieDbId),
}); });
} }
@ -169,10 +185,11 @@ namespace Ombi.Schedule.Jobs.Ombi
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex, Type = RecentlyAddedType.Plex,
ContentType = ContentType.Episode, ContentType = ContentType.Episode,
ContentId = p.Id ContentId = int.Parse(p.Series.TvDbId),
EpisodeNumber = p.EpisodeNumber,
SeasonNumber = p.SeasonNumber
}); });
} }
foreach (var e in embyContentMoviesToSend) foreach (var e in embyContentMoviesToSend)
{ {
if (e.Type == EmbyMediaType.Movie) if (e.Type == EmbyMediaType.Movie)
@ -182,7 +199,7 @@ namespace Ombi.Schedule.Jobs.Ombi
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby, Type = RecentlyAddedType.Emby,
ContentType = ContentType.Parent, ContentType = ContentType.Parent,
ContentId = e.Id ContentId = int.Parse(e.TheMovieDbId),
}); });
} }
} }
@ -194,11 +211,12 @@ namespace Ombi.Schedule.Jobs.Ombi
AddedAt = DateTime.Now, AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby, Type = RecentlyAddedType.Emby,
ContentType = ContentType.Episode, ContentType = ContentType.Episode,
ContentId = p.Id ContentId = int.Parse(p.Series.TvDbId),
EpisodeNumber = p.EpisodeNumber,
SeasonNumber = p.SeasonNumber
}); });
} }
await _recentlyAddedLog.AddRange(recentlyAddedLog); await _recentlyAddedLog.AddRange(recentlyAddedLog);
await Task.WhenAll(emailTasks.ToArray()); await Task.WhenAll(emailTasks.ToArray());
} }
else else
@ -221,6 +239,13 @@ namespace Ombi.Schedule.Jobs.Ombi
emailSettings); emailSettings);
} }
} }
}
catch (Exception e)
{
_log.LogError(e, "Error when attempting to create newsletter");
throw;
}
} }
public async Task Start() public async Task Start()
@ -229,6 +254,40 @@ namespace Ombi.Schedule.Jobs.Ombi
await Start(newsletterSettings, false); await Start(newsletterSettings, false);
} }
private HashSet<PlexEpisode> FilterPlexEpisodes(IEnumerable<PlexEpisode> source, IQueryable<RecentlyAddedLog> recentlyAdded)
{
var itemsToReturn = new HashSet<PlexEpisode>();
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<EmbyEpisode> FilterEmbyEpisodes(IEnumerable<EmbyEpisode> source, IQueryable<RecentlyAddedLog> recentlyAdded)
{
var itemsToReturn = new HashSet<EmbyEpisode>();
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) private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings, OmbiUser username)
{ {
var resolver = new NotificationMessageResolver(); var resolver = new NotificationMessageResolver();
@ -239,7 +298,7 @@ namespace Ombi.Schedule.Jobs.Ombi
return resolver.ParseMessage(template, curlys); return resolver.ParseMessage(template, curlys);
} }
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend, IQueryable<PlexEpisode> plexEpisodes, IQueryable<EmbyEpisode> embyEp, NewsletterSettings settings) private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend, HashSet<PlexEpisode> plexEpisodes, HashSet<EmbyEpisode> embyEp, NewsletterSettings settings)
{ {
var sb = new StringBuilder(); var sb = new StringBuilder();
@ -285,8 +344,7 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); _log.LogError(e, "Error when Processing Plex Movies {0}", info.Title);
throw;
} }
finally finally
{ {
@ -327,8 +385,7 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); _log.LogError(e, "Error when processing Emby Movies {0}", info.Title);
throw;
} }
finally finally
{ {
@ -366,7 +423,7 @@ namespace Ombi.Schedule.Jobs.Ombi
AddParagraph(sb, info.Overview); AddParagraph(sb, info.Overview);
} }
private async Task ProcessPlexTv(IQueryable<PlexEpisode> plexContent, StringBuilder sb) private async Task ProcessPlexTv(HashSet<PlexEpisode> plexContent, StringBuilder sb)
{ {
var series = new List<PlexServerContent>(); var series = new List<PlexServerContent>();
foreach (var plexEpisode in plexContent) foreach (var plexEpisode in plexContent)
@ -483,7 +540,7 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
catch (Exception e) catch (Exception e)
{ {
//Log.Error(e); _log.LogError(e, "Error when processing Plex TV {0}", t.Title);
} }
finally finally
{ {
@ -494,7 +551,7 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
private async Task ProcessEmbyTv(IQueryable<EmbyEpisode> embyContent, StringBuilder sb) private async Task ProcessEmbyTv(HashSet<EmbyEpisode> embyContent, StringBuilder sb)
{ {
var series = new List<EmbyContent>(); var series = new List<EmbyContent>();
foreach (var episode in embyContent) foreach (var episode in embyContent)
@ -584,7 +641,7 @@ namespace Ombi.Schedule.Jobs.Ombi
} }
catch (Exception e) catch (Exception e)
{ {
//Log.Error(e); _log.LogError(e, "Error when processing Emby TV {0}", t.Title);
} }
finally finally
{ {

@ -10,9 +10,9 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Dapper" Version="1.50.2" /> <PackageReference Include="Dapper" Version="1.50.2" />
<PackageReference Include="Hangfire" Version="1.6.17" /> <PackageReference Include="Hangfire" Version="1.6.19" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.17" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.6.19" />
<PackageReference Include="Hangfire.Console" Version="1.3.7" /> <PackageReference Include="Hangfire.Console" Version="1.3.10" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" /> <PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" /> <PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
<PackageReference Include="Hangfire.SQLite" Version="1.4.2" /> <PackageReference Include="Hangfire.SQLite" Version="1.4.2" />

@ -10,7 +10,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -7,7 +7,6 @@ namespace Ombi.Core.Settings.Models.External
{ {
public bool Enable { get; set; } public bool Enable { get; set; }
public List<PlexServers> Servers { get; set; } public List<PlexServers> Servers { get; set; }
} }
public class PlexServers : ExternalSettings public class PlexServers : ExternalSettings

@ -7,6 +7,6 @@ namespace Ombi.Settings.Settings.Models.Notifications
public bool DisableTv { get; set; } public bool DisableTv { get; set; }
public bool DisableMovies { get; set; } public bool DisableMovies { get; set; }
public bool Enabled { get; set; } public bool Enabled { get; set; }
public List<string> ExternalEmails { get; set; } public List<string> ExternalEmails { get; set; } = new List<string>();
} }
} }

@ -123,7 +123,23 @@ namespace Ombi.Store.Context
{ {
NormalizedName = OmbiRoles.RecievesNewsletter.ToUpper() 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 //Check if templates exist
var templates = NotificationTemplates.ToList(); var templates = NotificationTemplates.ToList();

@ -48,6 +48,7 @@ namespace Ombi.Store.Entities
public string TheMovieDbId { get; set; } public string TheMovieDbId { get; set; }
public string TvDbId { get; set; } public string TvDbId { get; set; }
public string Url { get; set; }
public ICollection<EmbyEpisode> Episodes { get; set; } public ICollection<EmbyEpisode> Episodes { get; set; }
} }

@ -8,7 +8,9 @@ namespace Ombi.Store.Entities
{ {
public RecentlyAddedType Type { get; set; } public RecentlyAddedType Type { get; set; }
public ContentType ContentType { 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; } public DateTime AddedAt { get; set; }
} }

@ -13,6 +13,7 @@ namespace Ombi.Store.Entities.Requests
public string Overview { get; set; } public string Overview { get; set; }
public string Title { get; set; } public string Title { get; set; }
public string PosterPath { get; set; } public string PosterPath { get; set; }
public string Background { get; set; }
public DateTime ReleaseDate { get; set; } public DateTime ReleaseDate { get; set; }
public string Status { get; set; } public string Status { get; set; }
/// <summary> /// <summary>

@ -29,6 +29,7 @@ namespace Ombi.Store.Entities
{ {
public enum UserType public enum UserType
{ {
SystemUser = 0,
LocalUser = 1, LocalUser = 1,
PlexUser = 2, PlexUser = 2,
EmbyUser = 3, EmbyUser = 3,

@ -0,0 +1,950 @@
// <auto-generated />
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<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

@ -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<string>(
name: "Background",
table: "TvRequests",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Background",
table: "TvRequests");
}
}
}

@ -0,0 +1,950 @@
// <auto-generated />
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<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

@ -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<string>(
name: "Url",
table: "EmbyContent",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Url",
table: "EmbyContent");
}
}
}

@ -0,0 +1,956 @@
// <auto-generated />
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<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Name")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("RoleId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType");
b.Property<string>("ClaimValue");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider");
b.Property<string>("ProviderKey");
b.Property<string>("ProviderDisplayName");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("RoleId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId");
b.Property<string>("LoginProvider");
b.Property<string>("Name");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Type");
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("ApplicationConfiguration");
});
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AuditArea");
b.Property<int>("AuditType");
b.Property<DateTime>("DateTime");
b.Property<string>("Description");
b.Property<string>("User");
b.HasKey("Id");
b.ToTable("Audit");
});
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("CouchPotatoCache");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId")
.IsRequired();
b.Property<string>("ImdbId");
b.Property<string>("ProviderId");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
});
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("EmbyId");
b.Property<int>("EpisodeNumber");
b.Property<string>("ImdbId");
b.Property<string>("ParentId");
b.Property<string>("ProviderId");
b.Property<int>("SeasonNumber");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.HasKey("Id");
b.HasIndex("ParentId");
b.ToTable("EmbyEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Content");
b.Property<string>("SettingsName");
b.HasKey("Id");
b.ToTable("GlobalSettings");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("Agent");
b.Property<bool>("Enabled");
b.Property<string>("Message");
b.Property<int>("NotificationType");
b.Property<string>("Subject");
b.HasKey("Id");
b.ToTable("NotificationTemplates");
});
modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("PlayerId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("NotificationUserId");
});
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount");
b.Property<string>("Alias");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken();
b.Property<string>("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed");
b.Property<string>("EmbyConnectUserId");
b.Property<int?>("EpisodeRequestLimit");
b.Property<DateTime?>("LastLoggedIn");
b.Property<bool>("LockoutEnabled");
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<int?>("MovieRequestLimit");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash");
b.Property<string>("PhoneNumber");
b.Property<bool>("PhoneNumberConfirmed");
b.Property<string>("ProviderUserId");
b.Property<string>("SecurityStamp");
b.Property<bool>("TwoFactorEnabled");
b.Property<string>("UserAccessToken");
b.Property<string>("UserName")
.HasMaxLength(256);
b.Property<int>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("GrandparentKey");
b.Property<int>("Key");
b.Property<int>("ParentKey");
b.Property<int>("SeasonNumber");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("GrandparentKey");
b.ToTable("PlexEpisode");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ParentKey");
b.Property<int>("PlexContentId");
b.Property<int?>("PlexServerContentId");
b.Property<int>("SeasonKey");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("PlexServerContentId");
b.ToTable("PlexSeasonsContent");
});
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<string>("ImdbId");
b.Property<int>("Key");
b.Property<string>("Quality");
b.Property<string>("ReleaseYear");
b.Property<string>("TheMovieDbId");
b.Property<string>("Title");
b.Property<string>("TvDbId");
b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id");
b.ToTable("PlexServerContent");
});
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("HasFile");
b.Property<int>("TheMovieDbId");
b.HasKey("Id");
b.ToTable("RadarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AddedAt");
b.Property<int>("ContentId");
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
b.ToTable("RecentlyAddedLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<int?>("IssueId");
b.Property<int>("ParentRequestId");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("SeriesType");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("ParentRequestId");
b.HasIndex("RequestedUserId");
b.ToTable("ChildRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Value");
b.HasKey("Id");
b.ToTable("IssueCategory");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Comment");
b.Property<DateTime>("Date");
b.Property<int?>("IssuesId");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("IssuesId");
b.HasIndex("UserId");
b.ToTable("IssueComments");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Description");
b.Property<int>("IssueCategoryId");
b.Property<int?>("IssueId");
b.Property<string>("ProviderId");
b.Property<int?>("RequestId");
b.Property<int>("RequestType");
b.Property<DateTime?>("ResovledDate");
b.Property<int>("Status");
b.Property<string>("Subject");
b.Property<string>("Title");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd();
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<string>("Background");
b.Property<bool?>("Denied");
b.Property<string>("DeniedReason");
b.Property<DateTime?>("DigitalReleaseDate");
b.Property<string>("ImdbId");
b.Property<int?>("IssueId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int>("RequestType");
b.Property<DateTime>("RequestedDate");
b.Property<string>("RequestedUserId");
b.Property<int>("RootPathOverride");
b.Property<string>("Status");
b.Property<int>("TheMovieDbId");
b.Property<string>("Title");
b.HasKey("Id");
b.HasIndex("RequestedUserId");
b.ToTable("MovieRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeCount");
b.Property<DateTime>("RequestDate");
b.Property<int>("RequestId");
b.Property<int>("RequestType");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("RequestLog");
});
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");
b.Property<string>("PosterPath");
b.Property<int?>("QualityOverride");
b.Property<DateTime>("ReleaseDate");
b.Property<int?>("RootFolder");
b.Property<string>("Status");
b.Property<string>("Title");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("TvRequests");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SickRageEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrCache");
});
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("EpisodeNumber");
b.Property<bool>("HasFile");
b.Property<int>("SeasonNumber");
b.Property<int>("TvDbId");
b.HasKey("Id");
b.ToTable("SonarrEpisodeCache");
});
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Token");
b.Property<string>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("Tokens");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("AirDate");
b.Property<bool>("Approved");
b.Property<bool>("Available");
b.Property<int>("EpisodeNumber");
b.Property<bool>("Requested");
b.Property<int>("SeasonId");
b.Property<string>("Title");
b.Property<string>("Url");
b.HasKey("Id");
b.HasIndex("SeasonId");
b.ToTable("EpisodeRequests");
});
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("ChildRequestId");
b.Property<int>("SeasonNumber");
b.HasKey("Id");
b.HasIndex("ChildRequestId");
b.ToTable("SeasonRequests");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Ombi.Store.Entities.OmbiUser")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", 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<string>", 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
}
}
}

@ -0,0 +1,35 @@
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<int>(
name: "EpisodeNumber",
table: "RecentlyAddedLog",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "SeasonNumber",
table: "RecentlyAddedLog",
nullable: true);
migrationBuilder.Sql("DELETE FROM RecentlyAddedLog");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "EpisodeNumber",
table: "RecentlyAddedLog");
migrationBuilder.DropColumn(
name: "SeasonNumber",
table: "RecentlyAddedLog");
}
}
}

@ -197,6 +197,8 @@ namespace Ombi.Store.Migrations
b.Property<int>("Type"); b.Property<int>("Type");
b.Property<string>("Url");
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("EmbyContent"); b.ToTable("EmbyContent");
@ -453,6 +455,10 @@ namespace Ombi.Store.Migrations
b.Property<int>("ContentType"); b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type"); b.Property<int>("Type");
b.HasKey("Id"); b.HasKey("Id");
@ -645,6 +651,8 @@ namespace Ombi.Store.Migrations
b.Property<int>("Id") b.Property<int>("Id")
.ValueGeneratedOnAdd(); .ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId"); b.Property<string>("ImdbId");
b.Property<string>("Overview"); b.Property<string>("Overview");

@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.2" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.9" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

@ -81,6 +81,7 @@ namespace Ombi.Store.Repository
public void Update(GlobalSettings entity) public void Update(GlobalSettings entity)
{ {
Db.Update(entity);
//_cache.Remove(GetName(entity.SettingsName)); //_cache.Remove(GetName(entity.SettingsName));
Db.SaveChanges(); Db.SaveChanges();
} }

@ -24,6 +24,7 @@ import { CookieComponent } from "./auth/cookie.component";
import { PageNotFoundComponent } from "./errors/not-found.component"; import { PageNotFoundComponent } from "./errors/not-found.component";
import { LandingPageComponent } from "./landingpage/landingpage.component"; import { LandingPageComponent } from "./landingpage/landingpage.component";
import { LoginComponent } from "./login/login.component"; import { LoginComponent } from "./login/login.component";
import { LoginOAuthComponent } from "./login/loginoauth.component";
import { ResetPasswordComponent } from "./login/resetpassword.component"; import { ResetPasswordComponent } from "./login/resetpassword.component";
import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component"; import { TokenResetPasswordComponent } from "./login/tokenresetpassword.component";
@ -41,6 +42,7 @@ const routes: Routes = [
{ path: "*", component: PageNotFoundComponent }, { path: "*", component: PageNotFoundComponent },
{ path: "", redirectTo: "/search", pathMatch: "full" }, { path: "", redirectTo: "/search", pathMatch: "full" },
{ path: "login", component: LoginComponent }, { path: "login", component: LoginComponent },
{ path: "Login/OAuth/:pin", component: LoginOAuthComponent },
{ path: "login/:landing", component: LoginComponent }, { path: "login/:landing", component: LoginComponent },
{ path: "reset", component: ResetPasswordComponent }, { path: "reset", component: ResetPasswordComponent },
{ path: "token", component: TokenResetPasswordComponent }, { path: "token", component: TokenResetPasswordComponent },
@ -116,6 +118,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
ResetPasswordComponent, ResetPasswordComponent,
TokenResetPasswordComponent, TokenResetPasswordComponent,
CookieComponent, CookieComponent,
LoginOAuthComponent,
], ],
providers: [ providers: [
NotificationService, NotificationService,

@ -2,6 +2,7 @@
username: string; username: string;
password: string; password: string;
rememberMe: boolean; rememberMe: boolean;
usePlexOAuth: boolean;
} }
export interface ILocalUser { export interface ILocalUser {

@ -18,6 +18,10 @@ export class AuthService extends ServiceHelpers {
return this.http.post(`${this.url}/`, JSON.stringify(login), {headers: this.headers}); return this.http.post(`${this.url}/`, JSON.stringify(login), {headers: this.headers});
} }
public oAuth(pin: number): Observable<any> {
return this.http.get<any>(`${this.url}/${pin}`, {headers: this.headers});
}
public requiresPassword(login: IUserLogin): Observable<boolean> { public requiresPassword(login: IUserLogin): Observable<boolean> {
return this.http.post<boolean>(`${this.url}/requirePassword`, JSON.stringify(login), {headers: this.headers}); return this.http.post<boolean>(`${this.url}/requirePassword`, JSON.stringify(login), {headers: this.headers});
} }

@ -2,6 +2,10 @@
user: IPlexUser; user: IPlexUser;
} }
export interface IPlexOAuthAccessToken {
accessToken: string;
}
export interface IPlexUser { export interface IPlexUser {
email: string; email: string;
uuid: string; uuid: string;

@ -21,6 +21,7 @@
requested: boolean; requested: boolean;
available: boolean; available: boolean;
plexUrl: string; plexUrl: string;
embyUrl: string;
quality: string; quality: string;
digitalReleaseDate: Date; digitalReleaseDate: Date;

@ -27,6 +27,7 @@ export interface ISearchTvResult {
requested: boolean; requested: boolean;
available: boolean; available: boolean;
plexUrl: string; plexUrl: string;
embyUrl: string;
firstSeason: boolean; firstSeason: boolean;
latestSeason: boolean; latestSeason: boolean;
} }

@ -7,16 +7,24 @@ include the remember me checkbox
<div *ngIf="background" @fadeInOut class="bg" [style.background-image]="background"></div> <div *ngIf="background" @fadeInOut class="bg" [style.background-image]="background"></div>
<div class="container" id="login"> <div class="container" id="login">
<div class="card card-container"> <div class="card card-container">
<!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> --> <!-- <img class="profile-img-card" src="//lh3.googleusercontent.com/-6V8xOA6M7BA/AAAAAAAAAAI/AAAAAAAAAAA/rzlHcD0KYwo/photo.jpg?sz=120" alt="" /> -->
<div *ngIf="!customizationSettings.logo"><img id="profile-img" class="profile-img-card" src="{{baseUrl}}/images/logo.png"/></div> <div *ngIf="!customizationSettings.logo">
<div *ngIf="customizationSettings.logo"><img id="profile-img" class="center" [src]="customizationSettings.logo" /></div> <img id="profile-img" class="profile-img-card" src="{{baseUrl}}/images/logo.png" />
</div>
<div *ngIf="customizationSettings.logo">
<img id="profile-img" class="center" [src]="customizationSettings.logo" />
</div>
<p id="profile-name" class="profile-name-card"></p> <p id="profile-name" class="profile-name-card"></p>
<div *ngIf="!plexEnabled || !customizationSettings.applicationUrl || loginWithOmbi">
<form *ngIf="authenticationSettings" class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)"> <form *ngIf="authenticationSettings" class="form-signin" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
<input type="email" id="inputEmail" class="form-control" formControlName="username" [attr.placeholder]="'Login.UsernamePlaceholder' | translate" autofocus> <input type="email" id="inputEmail" class="form-control" formControlName="username" [attr.placeholder]="'Login.UsernamePlaceholder' | translate"
<input *ngIf="!authenticationSettings.allowNoPassword" type="password" id="inputPassword" class="form-control" formControlName="password" [attr.placeholder]="'Login.PasswordPlaceholder' | translate"> autofocus>
<input *ngIf="!authenticationSettings.allowNoPassword" type="password" id="inputPassword" class="form-control" formControlName="password"
[attr.placeholder]="'Login.PasswordPlaceholder' | translate">
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="RememberMe" formControlName="rememberMe"> <input type="checkbox" id="RememberMe" formControlName="rememberMe">
@ -26,10 +34,25 @@ include the remember me checkbox
</div> </div>
</div> </div>
<button class="btn btn-success" type="submit" [translate]="'Login.SignInButton'"></button> <button class="btn btn-success" type="submit" [translate]="'Login.SignInButton'"></button>
</form><!-- /form -->
<a [routerLink]="['/reset']" class="forgot-password col-md-12"> <a [routerLink]="['/reset']" class="forgot-password col-md-12">
<b [translate]="'Login.ForgottenPassword'"></b> <b [translate]="'Login.ForgottenPassword'"></b>
</a> </a>
</div><!-- /card-container --> </form>
</div><!-- /container --> <!-- /form -->
</div>
<!-- Main OAuth Flow -->
<div class="form-signin" *ngIf="plexEnabled && customizationSettings.applicationUrl && !loginWithOmbi">
<button class="btn btn-success" type="button" (click)="loginWithOmbi = true">
Sign In With {{appName}}</button>
</div>
<div class="form-signin" *ngIf="plexEnabled && customizationSettings.applicationUrl && !loginWithOmbi">
<button class="btn btn-primary" type="button" (click)="oauth()">
Sign In With Plex</button>
</div>
</div>
<!-- /card-container -->
</div>
<!-- /container -->
</div> </div>

@ -25,9 +25,20 @@ export class LoginComponent implements OnDestroy, OnInit {
public form: FormGroup; public form: FormGroup;
public customizationSettings: ICustomizationSettings; public customizationSettings: ICustomizationSettings;
public authenticationSettings: IAuthenticationSettings; public authenticationSettings: IAuthenticationSettings;
public plexEnabled: boolean;
public background: any; public background: any;
public landingFlag: boolean; public landingFlag: boolean;
public baseUrl: string; public baseUrl: string;
public loginWithOmbi: boolean;
public get appName(): string {
if(this.customizationSettings.applicationName) {
return this.customizationSettings.applicationName;
} else {
return "Ombi";
}
}
private timer: any; private timer: any;
private errorBody: string; private errorBody: string;
@ -68,6 +79,7 @@ export class LoginComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x); this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x);
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x); this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.settingsService.getStatusPlex().subscribe(x => this.plexEnabled = x);
this.images.getRandomBackground().subscribe(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 + ")"); 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 +102,7 @@ export class LoginComponent implements OnDestroy, OnInit {
return; return;
} }
const value = form.value; 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 => { this.authService.requiresPassword(user).subscribe(x => {
if(x && this.authenticationSettings.allowNoPassword) { if(x && this.authenticationSettings.allowNoPassword) {
// Looks like this user requires a password // Looks like this user requires a password
@ -111,6 +123,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() { public ngOnDestroy() {
clearInterval(this.timer); clearInterval(this.timer);
} }
@ -124,5 +142,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 + ")"); .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 + ")");
}); });
} }
} }

@ -0,0 +1,16 @@

<div >
<div class="container" id="login">
<div class="card card-container">
<label>Please Wait...</label>
<div *ngIf="error">
<label>{{error}}</label>
<a [routerLink]="['/login']">
Back </a>
</div>
</div><!-- /card-container -->
</div><!-- /container -->
</div>

@ -0,0 +1,42 @@
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;
public error: string;
constructor(private authService: AuthService, private router: Router,
private route: ActivatedRoute) {
this.route.params
.subscribe((params: any) => {
this.pin = params.pin;
});
}
public ngOnInit(): void {
this.auth();
}
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;
}
});
}
}

@ -228,9 +228,14 @@ export class TvRequestsComponent implements OnInit {
} }
private loadBackdrop(val: TreeNode): void { private loadBackdrop(val: TreeNode): void {
if (val.data.background != null) {
val.data.background = this.sanitizer.bypassSecurityTrustStyle
("url(https://image.tmdb.org/t/p/w1280" + val.data.background + ")");
} else {
this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => { this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => {
val.data.background = this.sanitizer.bypassSecurityTrustStyle val.data.background = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")"); ("url(" + x + ")");
}); });
} }
} }
}

@ -85,6 +85,7 @@
<br/> <br/>
<div *ngIf="result.available"> <div *ngIf="result.available">
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a> <a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
<a *ngIf="result.embyUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a>
</div> </div>
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled"> <div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">

@ -118,13 +118,21 @@
<div *ngIf="node.data.fullyAvailable"> <div *ngIf="node.data.fullyAvailable">
<button style="text-align: right" class="btn btn-success-outline disabled" disabled> <button style="text-align: right" class="btn btn-success-outline disabled" disabled>
<i class="fa fa-check"></i> {{ 'Common.Available' | translate }}</button> <i class="fa fa-check"></i> {{ 'Common.Available' | translate }}
</button>
</div> </div>
<br /> <br />
<div *ngIf="node.data.plexUrl && node.data.available"> <div *ngIf="node.data.plexUrl && node.data.available">
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.plexUrl}}" <a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.plexUrl}}"
target="_blank"> target="_blank">
<i class="fa fa-eye"></i> {{ 'Search.ViewOnPlex' | translate }}</a> <i class="fa fa-eye"></i> {{ 'Search.ViewOnPlex' | translate }}
</a>
</div>
<div *ngIf="node.data.embyUrl && node.data.available">
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.embyUrl}}"
target="_blank">
<i class="fa fa-eye"></i> {{ 'Search.ViewOnEmby' | translate }}
</a>
</div> </div>
<div class="dropdown" *ngIf="issueCategories && issuesEnabled"> <div class="dropdown" *ngIf="issueCategories && issuesEnabled">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" <button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"

@ -4,3 +4,4 @@ export * from "./plex.service";
export * from "./radarr.service"; export * from "./radarr.service";
export * from "./sonarr.service"; export * from "./sonarr.service";
export * from "./tester.service"; export * from "./tester.service";
export * from "./plexoauth.service";

@ -29,4 +29,8 @@ export class PlexService extends ServiceHelpers {
public getFriends(): Observable<IUsersModel[]> { public getFriends(): Observable<IUsersModel[]> {
return this.http.get<IUsersModel[]>(`${this.url}Friends`, {headers: this.headers}); return this.http.get<IUsersModel[]>(`${this.url}Friends`, {headers: this.headers});
} }
public oAuth(wizard: boolean): Observable<any> {
return this.http.get<any>(`${this.url}oauth/${wizard}`, {headers: this.headers});
}
} }

@ -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<IPlexOAuthAccessToken> {
return this.http.get<IPlexOAuthAccessToken>(`${this.url}${pin}`, {headers: this.headers});
}
}

@ -71,6 +71,10 @@ export class SettingsService extends ServiceHelpers {
return this.http.get<IPlexSettings>(`${this.url}/Plex/`, {headers: this.headers}); return this.http.get<IPlexSettings>(`${this.url}/Plex/`, {headers: this.headers});
} }
public getStatusPlex(): Observable<boolean> {
return this.http.get<boolean>(`${this.url}/Plexstatus/`, {headers: this.headers});
}
public savePlex(settings: IPlexSettings): Observable<boolean> { public savePlex(settings: IPlexSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}/Plex/`, JSON.stringify(settings), {headers: this.headers}); return this.http.post<boolean>(`${this.url}/Plex/`, JSON.stringify(settings), {headers: this.headers});
} }

@ -21,7 +21,7 @@ export class CreateAdminComponent {
this.identityService.createWizardUser({username: this.username, password: this.password, usePlexAdminAccount: false}).subscribe(x => { this.identityService.createWizardUser({username: this.username, password: this.password, usePlexAdminAccount: false}).subscribe(x => {
if (x) { if (x) {
// Log me in. // 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); localStorage.setItem("id_token", c.access_token);

@ -17,9 +17,16 @@
<small>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</small> <small>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</small>
<div class="form-group"> <div class="form-group">
<div style="text-align: center; margin-top: 20px"> <div style="text-align: center; margin-top: 20px">
<button (click)="requestAuthToken()" class="btn btn-primary-outline">Request Token <i class="fa fa-key"></i></button> <button (click)="requestAuthToken()" class="btn btn-success-outline">Request Token <i class="fa fa-key"></i></button>
</div> </div>
</div> </div>
<p class="text-center">OR</p>
<div class="form-group">
<div style="text-align: center; margin-top: 20px">
<button (click)="oauth()" class="btn btn-primary" type="button">Continue With Plex</button>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -1,8 +1,6 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { ConfirmationService } from "primeng/primeng";
import { PlexService } from "../../services"; import { PlexService } from "../../services";
import { IdentityService, NotificationService, SettingsService } from "../../services"; import { IdentityService, NotificationService, SettingsService } from "../../services";
import { AuthService } from "./../../auth/auth.service"; import { AuthService } from "./../../auth/auth.service";
@ -17,7 +15,6 @@ export class PlexComponent {
constructor(private plexService: PlexService, private router: Router, constructor(private plexService: PlexService, private router: Router,
private notificationService: NotificationService, private notificationService: NotificationService,
private confirmationService: ConfirmationService,
private identityService: IdentityService, private identityService: IdentityService,
private settings: SettingsService, private settings: SettingsService,
private auth: AuthService) { } private auth: AuthService) { }
@ -28,25 +25,21 @@ export class PlexComponent {
this.notificationService.error("Username or password was incorrect. Could not authenticate with Plex."); this.notificationService.error("Username or password was incorrect. Could not authenticate with Plex.");
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({ this.identityService.createWizardUser({
username: "", username: "",
password: "", password: "",
usePlexAdminAccount: true, usePlexAdminAccount: true,
}).subscribe(x => { }).subscribe(y => {
if (x) { if (y) {
this.auth.login({ username: this.login, password: this.password, rememberMe:false }).subscribe(c => { this.auth.login({ username: this.login, password: this.password, rememberMe: false, usePlexOAuth: false }).subscribe(c => {
localStorage.setItem("id_token", c.access_token); localStorage.setItem("id_token", c.access_token);
// Mark that we have done the settings now // Mark that we have done the settings now
this.settings.getOmbi().subscribe(ombi => { this.settings.getOmbi().subscribe(ombi => {
ombi.wizard = true; ombi.wizard = true;
this.settings.saveOmbi(ombi).subscribe(x => { this.settings.saveOmbi(ombi).subscribe(s => {
this.settings.getUserManagementSettings().subscribe(usr => { this.settings.getUserManagementSettings().subscribe(usr => {
usr.importPlexAdmin = true; usr.importPlexAdmin = true;
@ -64,10 +57,14 @@ export class PlexComponent {
} }
}); });
}, },
reject: () => { );
this.router.navigate(["Wizard/CreateAdmin"]); }
},
}); public oauth() {
this.plexService.oAuth(true).subscribe(x => {
if(x.url) {
window.location.href = x.url;
}
}); });
} }
} }

@ -0,0 +1,14 @@

<img class="landing-header" src="/images/logo.png" width="300" />
<div class="landing-block shadow">
<div class="media">
<div id="contentBody" class="media-body">
<h4 class="media-heading landing-title">Plex Authentication</h4>
<div class="form-group">
<label for="username" class="control-label">Please Wait</label>
</div>
</div>
</div>
</div>
<p-confirmDialog></p-confirmDialog>

@ -0,0 +1,67 @@
import { Component, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router";
import { IdentityService, PlexOAuthService, 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 identityService: IdentityService,
private settings: SettingsService,
private router: Router,
private auth: AuthService) {
this.route.params
.subscribe((params: any) => {
this.pinId = params.pin;
});
}
public ngOnInit(): void {
this.plexOauth.oAuth(this.pinId).subscribe(x => {
if(!x.accessToken) {
return;
// RETURN
}
this.identityService.createWizardUser({
username: "",
password: "",
usePlexAdminAccount: true,
}).subscribe(u => {
if (u) {
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(s => {
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;
}
});
});
}
}

@ -14,6 +14,8 @@ import { WelcomeComponent } from "./welcome/welcome.component";
import { EmbyService } from "../services"; import { EmbyService } from "../services";
import { PlexService } from "../services"; import { PlexService } from "../services";
import { IdentityService } from "../services"; import { IdentityService } from "../services";
import { PlexOAuthService } from "../services";
import { PlexOAuthComponent } from "./plex/plexoauth.component";
const routes: Routes = [ const routes: Routes = [
{ path: "", component: WelcomeComponent}, { path: "", component: WelcomeComponent},
@ -21,6 +23,7 @@ const routes: Routes = [
{ path: "Plex", component: PlexComponent}, { path: "Plex", component: PlexComponent},
{ path: "Emby", component: EmbyComponent}, { path: "Emby", component: EmbyComponent},
{ path: "CreateAdmin", component: CreateAdminComponent}, { path: "CreateAdmin", component: CreateAdminComponent},
{ path: "OAuth/:pin", component: PlexOAuthComponent},
]; ];
@NgModule({ @NgModule({
imports: [ imports: [
@ -33,6 +36,7 @@ const routes: Routes = [
WelcomeComponent, WelcomeComponent,
MediaServerComponent, MediaServerComponent,
PlexComponent, PlexComponent,
PlexOAuthComponent,
CreateAdminComponent, CreateAdminComponent,
EmbyComponent, EmbyComponent,
], ],
@ -44,6 +48,7 @@ const routes: Routes = [
IdentityService, IdentityService,
EmbyService, EmbyService,
ConfirmationService, ConfirmationService,
PlexOAuthService,
], ],
}) })

@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.Plex; using Ombi.Api.Plex;
using Ombi.Api.Plex.Models; using Ombi.Api.Plex.Models;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Authentication;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
using Ombi.Helpers; using Ombi.Helpers;
@ -21,16 +22,18 @@ namespace Ombi.Controllers.External
public class PlexController : Controller public class PlexController : Controller
{ {
public PlexController(IPlexApi plexApi, ISettingsService<PlexSettings> plexSettings, public PlexController(IPlexApi plexApi, ISettingsService<PlexSettings> plexSettings,
ILogger<PlexController> logger) ILogger<PlexController> logger, IPlexOAuthManager manager)
{ {
PlexApi = plexApi; PlexApi = plexApi;
PlexSettings = plexSettings; PlexSettings = plexSettings;
_log = logger; _log = logger;
_plexOAuthManager = manager;
} }
private IPlexApi PlexApi { get; } private IPlexApi PlexApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; } private ISettingsService<PlexSettings> PlexSettings { get; }
private readonly ILogger<PlexController> _log; private readonly ILogger<PlexController> _log;
private readonly IPlexOAuthManager _plexOAuthManager;
/// <summary> /// <summary>
/// Signs into the Plex API. /// Signs into the Plex API.
@ -173,5 +176,37 @@ namespace Ombi.Controllers.External
// Filter out any dupes // Filter out any dupes
return vm.DistinctBy(x => x.Id); return vm.DistinctBy(x => x.Id);
} }
[HttpGet("oauth/{wizard:bool}")]
[AllowAnonymous]
public async Task<IActionResult> 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()});
}
} }
} }

@ -213,7 +213,7 @@ namespace Ombi.Controllers
[PowerUser] [PowerUser]
public async Task<IEnumerable<UserViewModel>> GetAllUsers() public async Task<IEnumerable<UserViewModel>> GetAllUsers()
{ {
var users = await UserManager.Users var users = await UserManager.Users.Where(x => x.UserType != UserType.SystemUser)
.ToListAsync(); .ToListAsync();
var model = new List<UserViewModel>(); var model = new List<UserViewModel>();

@ -0,0 +1,78 @@
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
{
[ApiExplorerSettings(IgnoreApi = true)]
[ApiV1]
[AllowAnonymous]
public class PlexOAuthController : Controller
{
public PlexOAuthController(IPlexOAuthManager manager, IPlexApi plexApi, ISettingsService<PlexSettings> plexSettings,
ILogger<PlexOAuthController> log)
{
_manager = manager;
_plexApi = plexApi;
_plexSettings = plexSettings;
_log = log;
}
private readonly IPlexOAuthManager _manager;
private readonly IPlexApi _plexApi;
private readonly ISettingsService<PlexSettings> _plexSettings;
private readonly ILogger _log;
[HttpGet("{pinId:int}")]
public async Task<IActionResult> 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)
{
_log.LogWarning("Looks like we can't find any Plex Servers");
}
_log.LogDebug("Adding first server");
settings.Enable = true;
settings.Servers = new List<PlexServers> {
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",
}
};
await _plexSettings.SaveSettingsAsync(settings);
return Json(new { accessToken });
}
}
}

@ -142,7 +142,19 @@ namespace Ombi.Controllers
[HttpGet("plex")] [HttpGet("plex")]
public async Task<PlexSettings> PlexSettings() public async Task<PlexSettings> PlexSettings()
{ {
return await Get<PlexSettings>(); var s = await Get<PlexSettings>();
return s;
}
[HttpGet("plexstatus")]
[AllowAnonymous]
public async Task<bool> PlexStatusSettings()
{
var s = await Get<PlexSettings>();
return s.Enable;
} }
/// <summary> /// <summary>
@ -274,12 +286,6 @@ namespace Ombi.Controllers
public async Task<IActionResult> GetThemeContent([FromQuery]string url) public async Task<IActionResult> GetThemeContent([FromQuery]string url)
{ {
var css = await _githubApi.GetThemesRawContent(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"); return Content(css, "text/css");
} }

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Ombi.Api;
using Ombi.Core.Authentication; using Ombi.Core.Authentication;
using Ombi.Helpers; using Ombi.Helpers;
using Ombi.Models; using Ombi.Models;
@ -23,18 +25,21 @@ namespace Ombi.Controllers
[Produces("application/json")] [Produces("application/json")]
public class TokenController public class TokenController
{ {
public TokenController(OmbiUserManager um, IOptions<TokenAuthentication> ta, IAuditRepository audit, ITokenRepository token) public TokenController(OmbiUserManager um, IOptions<TokenAuthentication> ta, IAuditRepository audit, ITokenRepository token,
IPlexOAuthManager oAuthManager)
{ {
_userManager = um; _userManager = um;
_tokenAuthenticationOptions = ta.Value; _tokenAuthenticationOptions = ta.Value;
_audit = audit; _audit = audit;
_token = token; _token = token;
_plexOAuthManager = oAuthManager;
} }
private readonly TokenAuthentication _tokenAuthenticationOptions; private readonly TokenAuthentication _tokenAuthenticationOptions;
private readonly IAuditRepository _audit; private readonly IAuditRepository _audit;
private readonly ITokenRepository _token; private readonly ITokenRepository _token;
private readonly OmbiUserManager _userManager; private readonly OmbiUserManager _userManager;
private readonly IPlexOAuthManager _plexOAuthManager;
/// <summary> /// <summary>
/// Gets the token. /// Gets the token.
@ -43,6 +48,8 @@ namespace Ombi.Controllers
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
public async Task<IActionResult> GetToken([FromBody] UserAuthModel model) public async Task<IActionResult> GetToken([FromBody] UserAuthModel model)
{
if (!model.UsePlexOAuth)
{ {
await _audit.Record(AuditType.None, AuditArea.Authentication, await _audit.Record(AuditType.None, AuditArea.Authentication,
$"UserName {model.Username} attempting to authenticate"); $"UserName {model.Username} attempting to authenticate");
@ -62,8 +69,36 @@ namespace Ombi.Controllers
user.EmailLogin = true; user.EmailLogin = true;
} }
// Verify Password // Verify Password
if (await _userManager.CheckPasswordAsync(user, model.Password)) if (await _userManager.CheckPasswordAsync(user, model.Password))
{
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
{
error = "Application URL has not been set"
});
}
return new JsonResult(new { url = url.ToString() });
}
return new UnauthorizedResult();
}
private async Task<IActionResult> CreateToken(bool rememberMe, OmbiUser user)
{ {
var roles = await _userManager.GetRolesAsync(user); var roles = await _userManager.GetRolesAsync(user);
@ -90,12 +125,12 @@ namespace Ombi.Controllers
var token = new JwtSecurityToken( var token = new JwtSecurityToken(
claims: claims, claims: claims,
expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5), expires: rememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5),
signingCredentials: creds, signingCredentials: creds,
audience: "Ombi", issuer: "Ombi" audience: "Ombi", issuer: "Ombi"
); );
var accessToken = new JwtSecurityTokenHandler().WriteToken(token); var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
if (model.RememberMe) if (rememberMe)
{ {
// Save the token so we can refresh it later // Save the token so we can refresh it later
//await _token.CreateToken(new Tokens() {Token = accessToken, User = user}); //await _token.CreateToken(new Tokens() {Token = accessToken, User = user});
@ -108,8 +143,39 @@ namespace Ombi.Controllers
}); });
} }
[HttpGet("{pinId:int}")]
public async Task<IActionResult> OAuth(int pinId)
{
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);
// 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 new UnauthorizedResult();
} }
}
return await CreateToken(true, user);
}
/// <summary> /// <summary>
/// Refreshes the token. /// Refreshes the token.

@ -6,5 +6,6 @@
public string Password { get; set; } public string Password { get; set; }
public bool RememberMe { get; set; } public bool RememberMe { get; set; }
public bool UsePlexAdminAccount { get; set; } public bool UsePlexAdminAccount { get; set; }
public bool UsePlexOAuth { get; set; }
} }
} }

@ -61,13 +61,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" /> <PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" /> <PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.17" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.6.19" />
<PackageReference Include="Hangfire.Console" Version="1.3.7" /> <PackageReference Include="Hangfire.Console" Version="1.3.10" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" /> <PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" /> <PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
<PackageReference Include="Hangfire.SQLite" Version="1.4.2" /> <PackageReference Include="Hangfire.SQLite" Version="1.4.2" />
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.6" /> <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.7" />
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.0.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.2" /> <PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="2.0.2" />
@ -78,7 +78,7 @@
<PackageReference Include="Serilog.Sinks.File" Version="3.2.0" /> <PackageReference Include="Serilog.Sinks.File" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.1-dev-00771" /> <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.1-dev-00771" />
<PackageReference Include="Serilog.Sinks.SQLite" Version="3.8.3" /> <PackageReference Include="Serilog.Sinks.SQLite" Version="3.8.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="1.0.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="2.4.0" />
<PackageReference Include="System.Security.Cryptography.Csp" Version="4.3.0" /> <PackageReference Include="System.Security.Cryptography.Csp" Version="4.3.0" />
</ItemGroup> </ItemGroup>

@ -82,7 +82,7 @@ namespace Ombi
ctx.SaveChanges(); ctx.SaveChanges();
} }
} }
else if(!baseUrl.Equals(dbBaseUrl.Value)) else if(baseUrl.HasValue() && !baseUrl.Equals(dbBaseUrl.Value))
{ {
dbBaseUrl.Value = baseUrl; dbBaseUrl.Value = baseUrl;
ctx.SaveChanges(); ctx.SaveChanges();

@ -10,6 +10,7 @@
"profiles": { "profiles": {
"IIS Express": { "IIS Express": {
"commandName": "IISExpress", "commandName": "IISExpress",
"commandLineArgs": "-baseurl /testing",
"launchBrowser": true, "launchBrowser": true,
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"

@ -173,12 +173,7 @@ namespace Ombi
settings.ApiKey = Guid.NewGuid().ToString("N"); settings.ApiKey = Guid.NewGuid().ToString("N");
ombiService.SaveSettings(settings); ombiService.SaveSettings(settings);
} }
if (settings.BaseUrl.HasValue())
{
app.UsePathBase(settings.BaseUrl);
}
else
{
// Check if it's in the startup args // Check if it's in the startup args
var appConfig = serviceProvider.GetService<IApplicationConfigRepository>(); var appConfig = serviceProvider.GetService<IApplicationConfigRepository>();
var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl).Result; var baseUrl = appConfig.Get(ConfigurationTypes.BaseUrl).Result;
@ -188,10 +183,11 @@ namespace Ombi
{ {
settings.BaseUrl = baseUrl.Value; settings.BaseUrl = baseUrl.Value;
ombiService.SaveSettings(settings); ombiService.SaveSettings(settings);
app.UsePathBase(settings.BaseUrl);
} }
} }
if (settings.BaseUrl.HasValue())
{
app.UsePathBase(settings.BaseUrl);
} }
app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 1, ServerTimeout = TimeSpan.FromDays(1), ShutdownTimeout = TimeSpan.FromDays(1)}); app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 1, ServerTimeout = TimeSpan.FromDays(1), ShutdownTimeout = TimeSpan.FromDays(1)});
@ -223,7 +219,6 @@ namespace Ombi
app.UseSwaggerUI(c => app.UseSwaggerUI(c =>
{ {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
c.ShowJsonEditor();
}); });
app.UseMvc(routes => app.UseMvc(routes =>

@ -89,6 +89,18 @@ O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i
@{ @{
if (customization.HasPresetTheme) if (customization.HasPresetTheme)
{ {
if (!string.IsNullOrEmpty(baseUrl))
{
if (!customization.PresetThemeContent.Contains("/" + baseUrl))
{
var index = customization.PresetThemeContent.IndexOf("/api/");
if (index > 0)
{
customization.PresetThemeContent = customization.PresetThemeContent.Insert(index, "/" + baseUrl);
}
}
}
<style> <style>
@Html.Raw(customization.PresetThemeContent) @Html.Raw(customization.PresetThemeContent)
</style> </style>

@ -76,6 +76,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Se på Plex", "ViewOnPlex": "Se på Plex",
"ViewOnEmby": "Se på Emby",
"RequestAdded": "{{title}} er anmodet med succes", "RequestAdded": "{{title}} er anmodet med succes",
"Similar": "Similar", "Similar": "Similar",
"Movies": { "Movies": {

@ -76,6 +76,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "In Plex anschauen", "ViewOnPlex": "In Plex anschauen",
"ViewOnEmby": "In Emby anschauen",
"RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt", "RequestAdded": "Anfrage für {{title}} wurde erfolgreich hinzugefügt",
"Similar": "Similar", "Similar": "Similar",
"Movies": { "Movies": {

@ -81,6 +81,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease":"Theatrical Release: {{date}}", "TheatricalRelease":"Theatrical Release: {{date}}",
"ViewOnPlex": "View On Plex", "ViewOnPlex": "View On Plex",
"ViewOnEmby": "View On Emby",
"RequestAdded": "Request for {{title}} has been added successfully", "RequestAdded": "Request for {{title}} has been added successfully",
"Similar":"Similar", "Similar":"Similar",
"Movies": { "Movies": {

@ -76,6 +76,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Ver en Plex", "ViewOnPlex": "Ver en Plex",
"ViewOnEmby": "Ver en Emby",
"RequestAdded": "La solicitud de {{title}} se ha agregado con éxito", "RequestAdded": "La solicitud de {{title}} se ha agregado con éxito",
"Similar": "Similar", "Similar": "Similar",
"Movies": { "Movies": {

@ -76,6 +76,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Regarder sur Plex", "ViewOnPlex": "Regarder sur Plex",
"ViewOnEmby": "Regarder sur Emby",
"RequestAdded": "La demande pour {{title}} a été ajoutée avec succès", "RequestAdded": "La demande pour {{title}} a été ajoutée avec succès",
"Similar": "Similar", "Similar": "Similar",
"Movies": { "Movies": {

@ -76,6 +76,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Guarda su Plex", "ViewOnPlex": "Guarda su Plex",
"ViewOnEmby": "Guarda su Emby",
"RequestAdded": "La richiesta per {{title}} è stata aggiunta correttamente", "RequestAdded": "La richiesta per {{title}} è stata aggiunta correttamente",
"Similar": "Similar", "Similar": "Similar",
"Movies": { "Movies": {

@ -76,6 +76,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Bekijk op Plex", "ViewOnPlex": "Bekijk op Plex",
"ViewOnEmby": "Bekijk op Emby",
"RequestAdded": "Aanvraag voor {{title}} is succesvol toegevoegd", "RequestAdded": "Aanvraag voor {{title}} is succesvol toegevoegd",
"Similar": "Similar", "Similar": "Similar",
"Movies": { "Movies": {

@ -76,6 +76,7 @@
"DigitalDate": "Digital utgivelse: {{date}}", "DigitalDate": "Digital utgivelse: {{date}}",
"TheatricalRelease": "Kinopremiere: {{date}}", "TheatricalRelease": "Kinopremiere: {{date}}",
"ViewOnPlex": "Spill av på Plex", "ViewOnPlex": "Spill av på Plex",
"ViewOnEmby": "Spill av på Emby",
"RequestAdded": "Forespørsel om {{title}} er lagt til", "RequestAdded": "Forespørsel om {{title}} er lagt til",
"Similar": "Lignende", "Similar": "Lignende",
"Movies": { "Movies": {

@ -76,6 +76,7 @@
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Visa på Plex", "ViewOnPlex": "Visa på Plex",
"ViewOnEmby": "Visa på Emby",
"RequestAdded": "Efterfrågan om {{title}} har lagts till", "RequestAdded": "Efterfrågan om {{title}} har lagts till",
"Similar": "Similar", "Similar": "Similar",
"Movies": { "Movies": {

Loading…
Cancel
Save