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>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
</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.Friends;
using Ombi.Api.Plex.Models.OAuth;
using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status;
@ -20,5 +22,8 @@ namespace Ombi.Api.Plex
Task<PlexFriends> GetUsers(string authToken);
Task<PlexAccount> GetAccount(string authToken);
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 Ombi.Api.Plex.Models;
using Ombi.Api.Plex.Models.Friends;
using Ombi.Api.Plex.Models.OAuth;
using Ombi.Api.Plex.Models.Server;
using Ombi.Api.Plex.Models.Status;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Settings.Settings.Models;
namespace Ombi.Api.Plex
{
public class PlexApi : IPlexApi
{
public PlexApi(IApi api)
public PlexApi(IApi api, ISettingsService<CustomizationSettings> settings)
{
Api = api;
_custom = settings;
}
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 FriendsUri = "https://plex.tv/pms/friends/all";
@ -156,6 +189,50 @@ namespace Ombi.Api.Plex
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>
/// Adds the required headers and also the authorization header
/// </summary>
@ -174,7 +251,7 @@ namespace Ombi.Api.Plex
private void AddHeaders(Request request)
{
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.AddContentHeader("Content-Type", request.ContentType == ContentType.Json ? "application/json" : "application/xml");
request.AddHeader("Accept", "application/json");

@ -1,4 +1,5 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Ombi.Api.Pushover.Models;
@ -17,7 +18,7 @@ namespace Ombi.Api.Pushover
public async Task<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);
return result;

@ -10,7 +10,7 @@
<ItemGroup>
<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="System.Xml.XmlSerializer" Version="4.3.0" />
</ItemGroup>

@ -10,7 +10,7 @@ namespace Ombi.Api
{
public Request()
{
}
public Request(string endpoint, string baseUrl, HttpMethod http, ContentType contentType = ContentType.Json)
@ -105,10 +105,10 @@ namespace Ombi.Api
hasQuery = true;
startingTag = builder.Query.Contains("?") ? "&" : "?";
}
builder.Query = hasQuery
? $"{builder.Query}{startingTag}{key}={value}"
: $"{startingTag}{key}={value}";
_modified = builder.Uri;
}

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

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

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

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

@ -11,12 +11,12 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.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.EntityFrameworkCore.Sqlite" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.5" />
<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" />
</ItemGroup>

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

@ -51,6 +51,7 @@ using Ombi.Store.Repository.Requests;
using Ombi.Updater;
using PlexContentCacher = Ombi.Schedule.Jobs.Plex;
using Ombi.Api.Telegram;
using Ombi.Core.Authentication;
using Ombi.Core.Processor;
using Ombi.Schedule.Jobs.Plex.Interfaces;
using Ombi.Schedule.Jobs.SickRage;
@ -82,6 +83,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>();
services.AddTransient<ITvSender, TvSender>();
services.AddTransient<IMassEmailSender, MassEmailSender>();
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
}
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;
}
}
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="Microsoft.Extensions.Caching.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="System.Security.Claims" Version="4.3.0" />
</ItemGroup>

@ -18,5 +18,19 @@ namespace Ombi.Helpers
return poster;
}
public static string FixBackgroundPath(string background)
{
// https://image.tmdb.org/t/p/w1280/fJAvGOitU8y53ByeHnM4avtKFaG.jpg
if (background.Contains("image.tmdb.org", CompareOptions.IgnoreCase))
{
// Somehow we have a full path here for the poster, we only want the last segment
var backgroundSegments = background.Split('/');
return backgroundSegments.Last();
}
return background;
}
}
}

@ -10,7 +10,7 @@
<ItemGroup>
<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>

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

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

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using MailKit;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.TheMovieDb;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Api.TvMaze;
@ -26,7 +29,7 @@ namespace Ombi.Schedule.Jobs.Ombi
public NewsletterJob(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> addedLog,
IMovieDbApi movieApi, ITvMazeApi tvApi, IEmailProvider email, ISettingsService<CustomizationSettings> custom,
ISettingsService<EmailNotificationSettings> emailSettings, INotificationTemplatesRepository templateRepo,
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter)
UserManager<OmbiUser> um, ISettingsService<NewsletterSettings> newsletter, ILogger<NewsletterJob> log)
{
_plex = plex;
_emby = emby;
@ -42,6 +45,7 @@ namespace Ombi.Schedule.Jobs.Ombi
_emailSettings.ClearCache();
_customizationSettings.ClearCache();
_newsletterSettings.ClearCache();
_log = log;
}
private readonly IPlexContentRepository _plex;
@ -55,6 +59,7 @@ namespace Ombi.Schedule.Jobs.Ombi
private readonly ISettingsService<EmailNotificationSettings> _emailSettings;
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
private readonly UserManager<OmbiUser> _userManager;
private readonly ILogger _log;
public async Task Start(NewsletterSettings settings, bool test)
{
@ -74,152 +79,172 @@ namespace Ombi.Schedule.Jobs.Ombi
return;
}
var customization = await _customizationSettings.GetSettingsAsync();
try
{
// Get the Content
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
var addedLog = _recentlyAddedLog.GetAll();
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
var customization = await _customizationSettings.GetSettingsAsync();
// Get the Content
var plexContent = _plex.GetAll().Include(x => x.Episodes).AsNoTracking();
var embyContent = _emby.GetAll().Include(x => x.Episodes).AsNoTracking();
var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode).Select(x => x.ContentId);
var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).Select(x => x.ContentId);
var addedLog = _recentlyAddedLog.GetAll();
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
// Filter out the ones that we haven't sent yet
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(x.Id));
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(x.Id));
var addedPlexEpisodesLogIds =
addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode);
var addedEmbyEpisodesLogIds =
addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode);
var plexEpisodesToSend = _plex.GetAllEpisodes().Include(x => x.Series).Where(x => !addedPlexEpisodesLogIds.Contains(x.Id)).AsNoTracking();
var embyEpisodesToSend = _emby.GetAllEpisodes().Include(x => x.Series).Where(x => !addedEmbyEpisodesLogIds.Contains(x.Id)).AsNoTracking();
// Filter out the ones that we haven't sent yet
var plexContentMoviesToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie && !addedPlexMovieLogIds.Contains(int.Parse(x.TheMovieDbId)));
var embyContentMoviesToSend = embyContent.Where(x => x.Type == EmbyMediaType.Movie && !addedEmbyMoviesLogIds.Contains(int.Parse(x.TheMovieDbId)));
_log.LogInformation("Plex Movies to send: {0}", plexContentMoviesToSend.Count());
_log.LogInformation("Emby Movies to send: {0}", embyContentMoviesToSend.Count());
var body = string.Empty;
if (test)
{
var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10);
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10);
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10);
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10);
body = await BuildHtml(plexm, embym, plext, embyt, settings);
}
else
{
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings);
if (body.IsNullOrEmpty())
{
return;
}
var plexEpisodesToSend =
FilterPlexEpisodes(_plex.GetAllEpisodes().Include(x => x.Series).AsNoTracking(), addedPlexEpisodesLogIds);
var embyEpisodesToSend = FilterEmbyEpisodes(_emby.GetAllEpisodes().Include(x => x.Series).AsNoTracking(),
addedEmbyEpisodesLogIds);
}
if (!test)
{
// Get the users to send it to
var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter);
if (!users.Any())
_log.LogInformation("Plex Episodes to send: {0}", plexEpisodesToSend.Count());
_log.LogInformation("Emby Episodes to send: {0}", embyEpisodesToSend.Count());
var body = string.Empty;
if (test)
{
return;
var plexm = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Movie).OrderByDescending(x => x.AddedAt).Take(10);
var embym = embyContent.Where(x => x.Type == EmbyMediaType.Movie).OrderByDescending(x => x.AddedAt).Take(10);
var plext = _plex.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.Series.AddedAt).Take(10).ToHashSet();
var embyt = _emby.GetAllEpisodes().Include(x => x.Series).OrderByDescending(x => x.AddedAt).Take(10).ToHashSet();
body = await BuildHtml(plexm, embym, plext, embyt, settings);
}
foreach (var emails in settings.ExternalEmails)
else
{
users.Add(new OmbiUser
body = await BuildHtml(plexContentMoviesToSend, embyContentMoviesToSend, plexEpisodesToSend, embyEpisodesToSend, settings);
if (body.IsNullOrEmpty())
{
UserName = emails,
Email = emails
});
return;
}
}
var emailTasks = new List<Task>();
foreach (var user in users)
if (!test)
{
if (user.Email.IsNullOrEmpty())
// Get the users to send it to
var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter);
if (!users.Any())
{
continue;
return;
}
foreach (var emails in settings.ExternalEmails)
{
users.Add(new OmbiUser
{
UserName = emails,
Email = emails
});
}
var emailTasks = new List<Task>();
foreach (var user in users)
{
// Get the users to send it to
if (user.Email.IsNullOrEmpty())
{
continue;
}
var messageContent = ParseTemplate(template, customization, user);
var email = new NewsletterTemplate();
var messageContent = ParseTemplate(template, customization, user);
var email = new NewsletterTemplate();
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
emailTasks.Add(_email.Send(
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email },
emailSettings));
}
emailTasks.Add(_email.Send(
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = user.Email },
emailSettings));
}
// Now add all of this to the Recently Added log
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
foreach (var p in plexContentMoviesToSend)
{
recentlyAddedLog.Add(new RecentlyAddedLog
// Now add all of this to the Recently Added log
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
foreach (var p in plexContentMoviesToSend)
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentType = ContentType.Parent,
ContentId = p.Id
});
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentType = ContentType.Parent,
ContentId = int.Parse(p.TheMovieDbId),
});
}
}
foreach (var p in plexEpisodesToSend)
{
recentlyAddedLog.Add(new RecentlyAddedLog
foreach (var p in plexEpisodesToSend)
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentType = ContentType.Episode,
ContentId = p.Id
});
}
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Plex,
ContentType = ContentType.Episode,
ContentId = int.Parse(p.Series.TvDbId),
EpisodeNumber = p.EpisodeNumber,
SeasonNumber = p.SeasonNumber
});
}
foreach (var e in embyContentMoviesToSend)
{
if (e.Type == EmbyMediaType.Movie)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby,
ContentType = ContentType.Parent,
ContentId = int.Parse(e.TheMovieDbId),
});
}
}
foreach (var e in embyContentMoviesToSend)
{
if (e.Type == EmbyMediaType.Movie)
foreach (var p in embyEpisodesToSend)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby,
ContentType = ContentType.Parent,
ContentId = e.Id
ContentType = ContentType.Episode,
ContentId = int.Parse(p.Series.TvDbId),
EpisodeNumber = p.EpisodeNumber,
SeasonNumber = p.SeasonNumber
});
}
await _recentlyAddedLog.AddRange(recentlyAddedLog);
await Task.WhenAll(emailTasks.ToArray());
}
foreach (var p in embyEpisodesToSend)
{
recentlyAddedLog.Add(new RecentlyAddedLog
{
AddedAt = DateTime.Now,
Type = RecentlyAddedType.Emby,
ContentType = ContentType.Episode,
ContentId = p.Id
});
}
await _recentlyAddedLog.AddRange(recentlyAddedLog);
await Task.WhenAll(emailTasks.ToArray());
}
else
{
var admins = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin);
foreach (var a in admins)
else
{
if (a.Email.IsNullOrEmpty())
var admins = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin);
foreach (var a in admins)
{
continue;
}
var messageContent = ParseTemplate(template, customization, a);
if (a.Email.IsNullOrEmpty())
{
continue;
}
var messageContent = ParseTemplate(template, customization, a);
var email = new NewsletterTemplate();
var email = new NewsletterTemplate();
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
var html = email.LoadTemplate(messageContent.Subject, messageContent.Message, body, customization.Logo);
await _email.Send(
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email },
emailSettings);
await _email.Send(
new NotificationMessage { Message = html, Subject = messageContent.Subject, To = a.Email },
emailSettings);
}
}
}
catch (Exception e)
{
_log.LogError(e, "Error when attempting to create newsletter");
throw;
}
}
@ -229,6 +254,40 @@ namespace Ombi.Schedule.Jobs.Ombi
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)
{
var resolver = new NotificationMessageResolver();
@ -239,7 +298,7 @@ namespace Ombi.Schedule.Jobs.Ombi
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();
@ -285,8 +344,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
_log.LogError(e, "Error when Processing Plex Movies {0}", info.Title);
}
finally
{
@ -315,7 +373,7 @@ namespace Ombi.Schedule.Jobs.Ombi
theMovieDbId = result.id.ToString();
}
var info = await _movieApi.GetMovieInformationWithExtraInfo(int.Parse(theMovieDbId));
if (info == null)
{
@ -327,8 +385,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
_log.LogError(e, "Error when processing Emby Movies {0}", info.Title);
}
finally
{
@ -366,7 +423,7 @@ namespace Ombi.Schedule.Jobs.Ombi
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>();
foreach (var plexEpisode in plexContent)
@ -437,7 +494,7 @@ namespace Ombi.Schedule.Jobs.Ombi
sb.Append("<tr>");
sb.Append(
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
var title = $"{t.Title} ({t.ReleaseYear})";
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
@ -483,7 +540,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
catch (Exception e)
{
//Log.Error(e);
_log.LogError(e, "Error when processing Plex TV {0}", t.Title);
}
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>();
foreach (var episode in embyContent)
@ -584,7 +641,7 @@ namespace Ombi.Schedule.Jobs.Ombi
}
catch (Exception e)
{
//Log.Error(e);
_log.LogError(e, "Error when processing Emby TV {0}", t.Title);
}
finally
{

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

@ -10,7 +10,7 @@
<ItemGroup>
<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>

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

@ -7,6 +7,6 @@ namespace Ombi.Settings.Settings.Models.Notifications
public bool DisableTv { get; set; }
public bool DisableMovies { get; set; }
public bool Enabled { get; set; }
public List<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()
});
SaveChanges();
}
// Make sure we have the API User
var apiUserExists = Users.Any(x => x.UserName.Equals("Api", StringComparison.CurrentCultureIgnoreCase));
if (!apiUserExists)
{
Users.Add(new OmbiUser
{
UserName = "Api",
UserType = UserType.SystemUser,
NormalizedUserName = "API",
});
SaveChanges();
}
//Check if templates exist
var templates = NotificationTemplates.ToList();

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

@ -8,7 +8,9 @@ namespace Ombi.Store.Entities
{
public RecentlyAddedType Type { get; set; }
public ContentType ContentType { get; set; }
public int ContentId { get; set; } // This is dependant on the type
public int ContentId { get; set; } // This is dependant on the type, it's either TMDBID or TVDBID
public int? EpisodeNumber { get; set; }
public int? SeasonNumber { get; set; }
public DateTime AddedAt { get; set; }
}

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

@ -29,6 +29,7 @@ namespace Ombi.Store.Entities
{
public enum UserType
{
SystemUser = 0,
LocalUser = 1,
PlexUser = 2,
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<string>("Url");
b.HasKey("Id");
b.ToTable("EmbyContent");
@ -453,6 +455,10 @@ namespace Ombi.Store.Migrations
b.Property<int>("ContentType");
b.Property<int?>("EpisodeNumber");
b.Property<int?>("SeasonNumber");
b.Property<int>("Type");
b.HasKey("Id");
@ -645,6 +651,8 @@ namespace Ombi.Store.Migrations
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Background");
b.Property<string>("ImdbId");
b.Property<string>("Overview");

@ -14,7 +14,7 @@
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" 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" />
</ItemGroup>
<ItemGroup>

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

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

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

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

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

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

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

@ -7,29 +7,52 @@ include the remember me checkbox
<div *ngIf="background" @fadeInOut class="bg" [style.background-image]="background"></div>
<div class="container" id="login">
<div class="card card-container">
<!-- <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"><img id="profile-img" class="center" [src]="customizationSettings.logo" /></div>
<div *ngIf="!customizationSettings.logo">
<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>
<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 *ngIf="!authenticationSettings.allowNoPassword" type="password" id="inputPassword" class="form-control" formControlName="password" [attr.placeholder]="'Login.PasswordPlaceholder' | translate">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="RememberMe" formControlName="rememberMe" >
<label for="RememberMe" [translate]="'Login.RememberMe'"></label>
<div *ngIf="!plexEnabled || !customizationSettings.applicationUrl || loginWithOmbi">
<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 *ngIf="!authenticationSettings.allowNoPassword" type="password" id="inputPassword" class="form-control" formControlName="password"
[attr.placeholder]="'Login.PasswordPlaceholder' | translate">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="RememberMe" formControlName="rememberMe">
<label for="RememberMe" [translate]="'Login.RememberMe'"></label>
</div>
</div>
</div>
<button class="btn btn-success" type="submit" [translate]="'Login.SignInButton'"></button>
</form><!-- /form -->
<a [routerLink]="['/reset']" class="forgot-password col-md-12">
<b [translate]="'Login.ForgottenPassword'"></b>
</a>
</div><!-- /card-container -->
</div><!-- /container -->
<button class="btn btn-success" type="submit" [translate]="'Login.SignInButton'"></button>
<a [routerLink]="['/reset']" class="forgot-password col-md-12">
<b [translate]="'Login.ForgottenPassword'"></b>
</a>
</form>
<!-- /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>

@ -25,9 +25,20 @@ export class LoginComponent implements OnDestroy, OnInit {
public form: FormGroup;
public customizationSettings: ICustomizationSettings;
public authenticationSettings: IAuthenticationSettings;
public plexEnabled: boolean;
public background: any;
public landingFlag: boolean;
public baseUrl: string;
public loginWithOmbi: boolean;
public get appName(): string {
if(this.customizationSettings.applicationName) {
return this.customizationSettings.applicationName;
} else {
return "Ombi";
}
}
private timer: any;
private errorBody: string;
@ -68,6 +79,7 @@ export class LoginComponent implements OnDestroy, OnInit {
public ngOnInit() {
this.settingsService.getAuthentication().subscribe(x => this.authenticationSettings = x);
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
this.settingsService.getStatusPlex().subscribe(x => this.plexEnabled = x);
this.images.getRandomBackground().subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(-10deg, transparent 20%, rgba(0,0,0,0.7) 20.0%, rgba(0,0,0,0.7) 80.0%, transparent 80%),url(" + x.url + ")");
});
@ -90,7 +102,7 @@ export class LoginComponent implements OnDestroy, OnInit {
return;
}
const value = form.value;
const user = { password: value.password, username: value.username, rememberMe:value.rememberMe };
const user = { password: value.password, username: value.username, rememberMe: value.rememberMe, usePlexOAuth: false };
this.authService.requiresPassword(user).subscribe(x => {
if(x && this.authenticationSettings.allowNoPassword) {
// Looks like this user requires a password
@ -111,6 +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() {
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 + ")");
});
}
}

@ -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 {
this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => {
if (val.data.background != null) {
val.data.background = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")");
("url(https://image.tmdb.org/t/p/w1280" + val.data.background + ")");
} else {
this.imageService.getTvBanner(val.data.tvDbId).subscribe(x => {
val.data.background = this.sanitizer.bypassSecurityTrustStyle
("url(" + x + ")");
});
}
}
}

@ -85,6 +85,7 @@
<br/>
<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.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 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">

@ -118,17 +118,25 @@
<div *ngIf="node.data.fullyAvailable">
<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>
<br />
<div *ngIf="node.data.plexUrl && node.data.available">
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.plexUrl}}"
target="_blank">
<i class="fa fa-eye"></i> {{ 'Search.ViewOnPlex' | translate }}</a>
</div>
<a style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.plexUrl}}"
target="_blank">
<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 class="dropdown" *ngIf="issueCategories && issuesEnabled">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
aria-expanded="true">
<i class="fa fa-plus"></i> {{ 'Requests.ReportIssue' | translate }}
<span class="caret"></span>
</button>
@ -139,8 +147,8 @@
</ul>
</div>
<div *ngIf="!node.data.available">
<br/>
<br/>
<br />
<br />
</div>
</div>

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

@ -29,4 +29,8 @@ export class PlexService extends ServiceHelpers {
public getFriends(): Observable<IUsersModel[]> {
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});
}
public getStatusPlex(): Observable<boolean> {
return this.http.get<boolean>(`${this.url}/Plexstatus/`, {headers: this.headers});
}
public savePlex(settings: IPlexSettings): Observable<boolean> {
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 => {
if (x) {
// Log me in.
this.auth.login({ username: this.username, password: this.password, rememberMe:false }).subscribe(c => {
this.auth.login({ username: this.username, password: this.password, rememberMe: false, usePlexOAuth:false }).subscribe(c => {
localStorage.setItem("id_token", c.access_token);

@ -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>
<div class="form-group">
<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>
<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>

@ -1,8 +1,6 @@
import { Component } from "@angular/core";
import { Router } from "@angular/router";
import { ConfirmationService } from "primeng/primeng";
import { PlexService } from "../../services";
import { IdentityService, NotificationService, SettingsService } from "../../services";
import { AuthService } from "./../../auth/auth.service";
@ -17,7 +15,6 @@ export class PlexComponent {
constructor(private plexService: PlexService, private router: Router,
private notificationService: NotificationService,
private confirmationService: ConfirmationService,
private identityService: IdentityService,
private settings: SettingsService,
private auth: AuthService) { }
@ -28,25 +25,21 @@ export class PlexComponent {
this.notificationService.error("Username or password was incorrect. Could not authenticate with Plex.");
return;
}
this.confirmationService.confirm({
message: "Do you want your Plex user to be the main admin account on Ombi?",
header: "Use Plex Account",
icon: "fa fa-check",
accept: () => {
this.identityService.createWizardUser({
this.identityService.createWizardUser({
username: "",
password: "",
usePlexAdminAccount: true,
}).subscribe(x => {
if (x) {
this.auth.login({ username: this.login, password: this.password, rememberMe:false }).subscribe(c => {
}).subscribe(y => {
if (y) {
this.auth.login({ username: this.login, password: this.password, rememberMe: false, usePlexOAuth: false }).subscribe(c => {
localStorage.setItem("id_token", c.access_token);
// Mark that we have done the settings now
this.settings.getOmbi().subscribe(ombi => {
ombi.wizard = true;
this.settings.saveOmbi(ombi).subscribe(x => {
this.settings.saveOmbi(ombi).subscribe(s => {
this.settings.getUserManagementSettings().subscribe(usr => {
usr.importPlexAdmin = true;
@ -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 { PlexService } from "../services";
import { IdentityService } from "../services";
import { PlexOAuthService } from "../services";
import { PlexOAuthComponent } from "./plex/plexoauth.component";
const routes: Routes = [
{ path: "", component: WelcomeComponent},
@ -21,6 +23,7 @@ const routes: Routes = [
{ path: "Plex", component: PlexComponent},
{ path: "Emby", component: EmbyComponent},
{ path: "CreateAdmin", component: CreateAdminComponent},
{ path: "OAuth/:pin", component: PlexOAuthComponent},
];
@NgModule({
imports: [
@ -33,6 +36,7 @@ const routes: Routes = [
WelcomeComponent,
MediaServerComponent,
PlexComponent,
PlexOAuthComponent,
CreateAdminComponent,
EmbyComponent,
],
@ -44,6 +48,7 @@ const routes: Routes = [
IdentityService,
EmbyService,
ConfirmationService,
PlexOAuthService,
],
})

@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging;
using Ombi.Api.Plex;
using Ombi.Api.Plex.Models;
using Ombi.Attributes;
using Ombi.Core.Authentication;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
@ -21,16 +22,18 @@ namespace Ombi.Controllers.External
public class PlexController : Controller
{
public PlexController(IPlexApi plexApi, ISettingsService<PlexSettings> plexSettings,
ILogger<PlexController> logger)
ILogger<PlexController> logger, IPlexOAuthManager manager)
{
PlexApi = plexApi;
PlexSettings = plexSettings;
_log = logger;
_plexOAuthManager = manager;
}
private IPlexApi PlexApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private readonly ILogger<PlexController> _log;
private readonly IPlexOAuthManager _plexOAuthManager;
/// <summary>
/// Signs into the Plex API.
@ -173,5 +176,37 @@ namespace Ombi.Controllers.External
// Filter out any dupes
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]
public async Task<IEnumerable<UserViewModel>> GetAllUsers()
{
var users = await UserManager.Users
var users = await UserManager.Users.Where(x => x.UserType != UserType.SystemUser)
.ToListAsync();
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")]
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>
@ -274,12 +286,6 @@ namespace Ombi.Controllers
public async Task<IActionResult> GetThemeContent([FromQuery]string url)
{
var css = await _githubApi.GetThemesRawContent(url);
var ombiSettings = await OmbiSettings();
if (ombiSettings.BaseUrl != null)
{
int index = css.IndexOf("/api/");
css = css.Insert(index, ombiSettings.BaseUrl);
}
return Content(css, "text/css");
}

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
@ -9,6 +10,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Ombi.Api;
using Ombi.Core.Authentication;
using Ombi.Helpers;
using Ombi.Models;
@ -23,18 +25,21 @@ namespace Ombi.Controllers
[Produces("application/json")]
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;
_tokenAuthenticationOptions = ta.Value;
_audit = audit;
_token = token;
_plexOAuthManager = oAuthManager;
}
private readonly TokenAuthentication _tokenAuthenticationOptions;
private readonly IAuditRepository _audit;
private readonly ITokenRepository _token;
private readonly OmbiUserManager _userManager;
private readonly IPlexOAuthManager _plexOAuthManager;
/// <summary>
/// Gets the token.
@ -44,71 +49,132 @@ namespace Ombi.Controllers
[HttpPost]
public async Task<IActionResult> GetToken([FromBody] UserAuthModel model)
{
await _audit.Record(AuditType.None, AuditArea.Authentication,
if (!model.UsePlexOAuth)
{
await _audit.Record(AuditType.None, AuditArea.Authentication,
$"UserName {model.Username} attempting to authenticate");
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null)
{
// Could this be an email login?
user = await _userManager.FindByEmailAsync(model.Username);
var user = await _userManager.FindByNameAsync(model.Username);
if (user == null)
{
return new UnauthorizedResult();
}
// Could this be an email login?
user = await _userManager.FindByEmailAsync(model.Username);
user.EmailLogin = true;
}
if (user == null)
{
return new UnauthorizedResult();
}
user.EmailLogin = true;
}
// Verify Password
if (await _userManager.CheckPasswordAsync(user, model.Password))
{
var roles = await _userManager.GetRolesAsync(user);
if (roles.Contains(OmbiRoles.Disabled))
// Verify Password
if (await _userManager.CheckPasswordAsync(user, model.Password))
{
return new UnauthorizedResult();
return await CreateToken(model.RememberMe, user);
}
user.LastLoggedIn = DateTime.UtcNow;
await _userManager.UpdateAsync(user);
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
claims.AddRange(roles.Select(role => new Claim("role", role)));
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
claims: claims,
expires: model.RememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5),
signingCredentials: creds,
audience: "Ombi", issuer:"Ombi"
);
var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
if (model.RememberMe)
}
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)
{
// Save the token so we can refresh it later
//await _token.CreateToken(new Tokens() {Token = accessToken, User = user});
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);
if (roles.Contains(OmbiRoles.Disabled))
{
return new UnauthorizedResult();
}
user.LastLoggedIn = DateTime.UtcNow;
await _userManager.UpdateAsync(user);
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
new Claim(ClaimTypes.NameIdentifier, user.Id),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
};
claims.AddRange(roles.Select(role => new Claim("role", role)));
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_tokenAuthenticationOptions.SecretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
claims: claims,
expires: rememberMe ? DateTime.UtcNow.AddDays(7) : DateTime.UtcNow.AddHours(5),
signingCredentials: creds,
audience: "Ombi", issuer: "Ombi"
);
var accessToken = new JwtSecurityTokenHandler().WriteToken(token);
if (rememberMe)
{
// Save the token so we can refresh it later
//await _token.CreateToken(new Tokens() {Token = accessToken, User = user});
}
return new JsonResult(new
{
access_token = accessToken,
expiration = token.ValidTo
});
}
[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
{
access_token = accessToken,
expiration = token.ValidTo
errorMessage = "Could not authenticate with Plex"
});
}
return new UnauthorizedResult();
// Let's look for the users account
var account = await _plexOAuthManager.GetAccount(accessToken);
// Get the ombi user
var user = await _userManager.FindByNameAsync(account.user.username);
if (user == null)
{
// Could this be an email login?
user = await _userManager.FindByEmailAsync(account.user.email);
if (user == null)
{
return new UnauthorizedResult();
}
}
return await CreateToken(true, user);
}
/// <summary>
@ -127,7 +193,7 @@ namespace Ombi.Controllers
{
return new UnauthorizedResult();
}
throw new NotImplementedException();
}

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

@ -61,13 +61,13 @@
<ItemGroup>
<PackageReference Include="AutoMapper" Version="6.1.1" />
<PackageReference Include="CommandLineParser" Version="2.1.1-beta" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.17" />
<PackageReference Include="Hangfire.Console" Version="1.3.7" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.6.19" />
<PackageReference Include="Hangfire.Console" Version="1.3.10" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
<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.Logging.Debug" Version="2.0.1" />
<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.RollingFile" Version="3.3.1-dev-00771" />
<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" />
</ItemGroup>

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

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

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

@ -17,56 +17,56 @@
}
var appName = customization.ApplicationName;
if(string.IsNullOrEmpty(appName))
if (string.IsNullOrEmpty(appName))
{
appName = "Ombi";
}
}
<!DOCTYPE html>
<html>
<head>
<!--
bbbbbbbb
OOOOOOOOO b::::::b iiii
OO:::::::::OO b::::::b i::::i
OO:::::::::::::OO b::::::b iiii
O:::::::OOO:::::::O b:::::b
O::::::O O::::::O mmmmmmm mmmmmmm b:::::bbbbbbbbb iiiiiii
O:::::O O:::::O mm:::::::m m:::::::mm b::::::::::::::bb i:::::i
O:::::O O:::::Om::::::::::mm::::::::::mb::::::::::::::::b i::::i
O:::::O O:::::Om::::::::::::::::::::::mb:::::bbbbb:::::::b i::::i
O:::::O O:::::Om:::::mmm::::::mmm:::::mb:::::b b::::::b i::::i
O:::::O O:::::Om::::m m::::m m::::mb:::::b b:::::b i::::i
O:::::O O:::::Om::::m m::::m m::::mb:::::b b:::::b i::::i
O::::::O O::::::Om::::m m::::m m::::mb:::::b b:::::b i::::i
O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i
OO:::::::::::::OO m::::m m::::m m::::mb::::::::::::::::b i::::::i
OO:::::::::OO m::::m m::::m m::::mb:::::::::::::::b i::::::i
OOOOOOOOO mmmmmm mmmmmm mmmmmmbbbbbbbbbbbbbbbb iiiiiiii
-->
<meta charset="utf-8"/>
<!--
bbbbbbbb
OOOOOOOOO b::::::b iiii
OO:::::::::OO b::::::b i::::i
OO:::::::::::::OO b::::::b iiii
O:::::::OOO:::::::O b:::::b
O::::::O O::::::O mmmmmmm mmmmmmm b:::::bbbbbbbbb iiiiiii
O:::::O O:::::O mm:::::::m m:::::::mm b::::::::::::::bb i:::::i
O:::::O O:::::Om::::::::::mm::::::::::mb::::::::::::::::b i::::i
O:::::O O:::::Om::::::::::::::::::::::mb:::::bbbbb:::::::b i::::i
O:::::O O:::::Om:::::mmm::::::mmm:::::mb:::::b b::::::b i::::i
O:::::O O:::::Om::::m m::::m m::::mb:::::b b:::::b i::::i
O:::::O O:::::Om::::m m::::m m::::mb:::::b b:::::b i::::i
O::::::O O::::::Om::::m m::::m m::::mb:::::b b:::::b i::::i
O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i
OO:::::::::::::OO m::::m m::::m m::::mb::::::::::::::::b i::::::i
OO:::::::::OO m::::m m::::m m::::mb:::::::::::::::b i::::::i
OOOOOOOOO mmmmmm mmmmmm mmmmmmbbbbbbbbbbbbbbbb iiiiiiii
-->
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="description" content="Ombi, media request tool">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@appName</title>
<meta property="og:title" content=“@appName”/>
<meta property="og:image" content="~/images/logo.png"/>
<meta property="og:site_name" content=“@appName”/>
<base href="/@baseUrl"/>
<meta property="og:title" content=“@appName” />
<meta property="og:image" content="~/images/logo.png" />
<meta property="og:site_name" content=“@appName” />
<base href="/@baseUrl" />
<link rel="apple-touch-icon" sizes="180x180" href="~/images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="~/images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="~/images/favicon/favicon-16x16.png">
@ -80,23 +80,35 @@ O:::::::OOO:::::::Om::::m m::::m m::::mb:::::bbbbbb::::::bi::::::i
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="~/loading.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/loading.css" asp-append-version="true" />
<link rel="stylesheet" href="~/dist/vendor.css" asp-append-version="true" />
<script src="~/dist/vendor.js" asp-append-version="true" defer></script>
<script src="~/dist/main.js" asp-append-version="true" defer></script>
</head>
<body>
@{
@{
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>
@Html.Raw(customization.PresetThemeContent)
</style>
</style>
}
if (!string.IsNullOrEmpty(customization.CustomCssLink))
{
<link rel="stylesheet" href="@customization.CustomCssLink" asp-append-version="true"/>
<link rel="stylesheet" href="@customization.CustomCssLink" asp-append-version="true" />
}
}

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save