mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-14 01:02:57 -07:00
Merge branch 'develop' of https://github.com/tidusjar/ombi into develop
This commit is contained in:
commit
169ad7da8b
71 changed files with 4457 additions and 453 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -2,6 +2,26 @@
|
|||
|
||||
## (unreleased)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Added the ability to refresh out backend metadata (#2078) [Jamie]
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- Fixed #2074 and #2079. [Jamie]
|
||||
|
||||
- Small changes to the auto updater, let's see how this works. [Jamie]
|
||||
|
||||
|
||||
## v3.0.3030 (2018-03-14)
|
||||
|
||||
### **New Features**
|
||||
|
||||
- Updated the .Net core dependancies #2072. [Jamie]
|
||||
|
||||
|
||||
## v3.0.3020 (2018-03-13)
|
||||
|
||||
### **Fixes**
|
||||
|
||||
- Small memory improvements in the Plex Sync. [Jamie]
|
||||
|
@ -22,6 +42,8 @@
|
|||
|
||||
- Experimental, set the Webpack base root to the ombi base path if we have it. This should hopefully fix the reverse proxy issues. [Jamie]
|
||||
|
||||
- Fixed #2056. [tidusjar]
|
||||
|
||||
|
||||
## v3.0.3000 (2018-03-09)
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@ namespace Ombi.Api.FanartTv
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<MovieResult> GetMovieImages(int theMovieDbId, string token)
|
||||
public async Task<MovieResult> GetMovieImages(string movieOrImdbId, string token)
|
||||
{
|
||||
var request = new Request($"movies/{theMovieDbId}", Endpoint, HttpMethod.Get);
|
||||
var request = new Request($"movies/{movieOrImdbId}", Endpoint, HttpMethod.Get);
|
||||
request.AddHeader("api-key", token);
|
||||
|
||||
return await Api.Request<MovieResult>(request);
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace Ombi.Api.FanartTv
|
|||
{
|
||||
public interface IFanartTvApi
|
||||
{
|
||||
Task<MovieResult> GetMovieImages(int theMovieDbId, string token);
|
||||
Task<MovieResult> GetMovieImages(string movieOrImdbId, string token);
|
||||
Task<TvResult> GetTvImages(int tvdbId, string token);
|
||||
}
|
||||
}
|
16
src/Ombi.Core/Engine/IRecentlyAddedEngine.cs
Normal file
16
src/Ombi.Core/Engine/IRecentlyAddedEngine.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public interface IRecentlyAddedEngine
|
||||
{
|
||||
IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies();
|
||||
IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to);
|
||||
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason);
|
||||
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(bool groupBySeason);
|
||||
Task<bool> UpdateRecentlyAddedDatabase();
|
||||
}
|
||||
}
|
224
src/Ombi.Core/Engine/RecentlyAddedEngine.cs
Normal file
224
src/Ombi.Core/Engine/RecentlyAddedEngine.cs
Normal file
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using RecentlyAddedType = Ombi.Store.Entities.RecentlyAddedType;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
{
|
||||
public class RecentlyAddedEngine : IRecentlyAddedEngine
|
||||
{
|
||||
public RecentlyAddedEngine(IPlexContentRepository plex, IEmbyContentRepository emby, IRepository<RecentlyAddedLog> recentlyAdded)
|
||||
{
|
||||
_plex = plex;
|
||||
_emby = emby;
|
||||
_recentlyAddedLog = recentlyAdded;
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plex;
|
||||
private readonly IEmbyContentRepository _emby;
|
||||
private readonly IRepository<RecentlyAddedLog> _recentlyAddedLog;
|
||||
|
||||
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to)
|
||||
{
|
||||
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie && x.AddedAt > from && x.AddedAt < to);
|
||||
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie && x.AddedAt > from && x.AddedAt < to);
|
||||
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies).Take(30);
|
||||
}
|
||||
|
||||
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies()
|
||||
{
|
||||
var plexMovies = _plex.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Movie);
|
||||
var embyMovies = _emby.GetAll().Where(x => x.Type == EmbyMediaType.Movie);
|
||||
return GetRecentlyAddedMovies(plexMovies, embyMovies);
|
||||
}
|
||||
|
||||
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason)
|
||||
{
|
||||
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show && x.AddedAt > from && x.AddedAt < to);
|
||||
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series && x.AddedAt > from && x.AddedAt < to);
|
||||
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason).Take(30);
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(bool groupBySeason)
|
||||
{
|
||||
var plexTv = _plex.GetAll().Include(x => x.Seasons).Include(x => x.Episodes).Where(x => x.Type == PlexMediaTypeEntity.Show);
|
||||
var embyTv = _emby.GetAll().Include(x => x.Episodes).Where(x => x.Type == EmbyMediaType.Series);
|
||||
|
||||
return GetRecentlyAddedTv(plexTv, embyTv, groupBySeason);
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateRecentlyAddedDatabase()
|
||||
{
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes);
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes);
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
foreach (var p in plexContent)
|
||||
{
|
||||
if (p.Type == PlexMediaTypeEntity.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = p.Id,
|
||||
ContentType = ContentType.Parent
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in p.Episodes)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = ep.Id,
|
||||
ContentType = ContentType.Episode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in embyContent)
|
||||
{
|
||||
if (e.Type == EmbyMediaType.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentId = e.Id,
|
||||
ContentType = ContentType.Parent
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in e.Episodes)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentId = ep.Id,
|
||||
ContentType = ContentType.Episode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(IQueryable<PlexServerContent> plexTv, IQueryable<EmbyContent> embyTv,
|
||||
bool groupBySeason)
|
||||
{
|
||||
var model = new HashSet<RecentlyAddedTvModel>();
|
||||
TransformPlexShows(plexTv, model);
|
||||
TransformEmbyShows(embyTv, model);
|
||||
|
||||
if (groupBySeason)
|
||||
{
|
||||
return model.DistinctBy(x => x.SeasonNumber);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(IQueryable<PlexServerContent> plexMovies, IQueryable<EmbyContent> embyMovies)
|
||||
{
|
||||
var model = new HashSet<RecentlyAddedMovieModel>();
|
||||
TransformPlexMovies(plexMovies, model);
|
||||
TransformEmbyMovies(embyMovies, model);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private static void TransformEmbyMovies(IQueryable<EmbyContent> embyMovies, HashSet<RecentlyAddedMovieModel> model)
|
||||
{
|
||||
foreach (var emby in embyMovies)
|
||||
{
|
||||
model.Add(new RecentlyAddedMovieModel
|
||||
{
|
||||
Id = emby.Id,
|
||||
ImdbId = emby.ProviderId,
|
||||
AddedAt = emby.AddedAt,
|
||||
Title = emby.Title,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformPlexMovies(IQueryable<PlexServerContent> plexMovies, HashSet<RecentlyAddedMovieModel> model)
|
||||
{
|
||||
foreach (var plex in plexMovies)
|
||||
{
|
||||
model.Add(new RecentlyAddedMovieModel
|
||||
{
|
||||
Id = plex.Id,
|
||||
ImdbId = plex.ImdbId,
|
||||
TheMovieDbId = plex.TheMovieDbId,
|
||||
AddedAt = plex.AddedAt,
|
||||
Title = plex.Title,
|
||||
Quality = plex.Quality,
|
||||
ReleaseYear = plex.ReleaseYear
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformPlexShows(IQueryable<PlexServerContent> plexShows, HashSet<RecentlyAddedTvModel> model)
|
||||
{
|
||||
foreach (var plex in plexShows)
|
||||
{
|
||||
foreach (var season in plex.Seasons)
|
||||
{
|
||||
foreach (var episode in plex.Episodes)
|
||||
{
|
||||
model.Add(new RecentlyAddedTvModel
|
||||
{
|
||||
Id = plex.Id,
|
||||
ImdbId = plex.ImdbId,
|
||||
TheMovieDbId = plex.TheMovieDbId,
|
||||
AddedAt = plex.AddedAt,
|
||||
Title = plex.Title,
|
||||
Quality = plex.Quality,
|
||||
ReleaseYear = plex.ReleaseYear,
|
||||
TvDbId = plex.TvDbId,
|
||||
EpisodeNumber = episode.EpisodeNumber,
|
||||
SeasonNumber = season.SeasonNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransformEmbyShows(IQueryable<EmbyContent> embyShows, HashSet<RecentlyAddedTvModel> model)
|
||||
{
|
||||
foreach (var emby in embyShows)
|
||||
{
|
||||
foreach (var episode in emby.Episodes)
|
||||
{
|
||||
model.Add(new RecentlyAddedTvModel
|
||||
{
|
||||
Id = emby.Id,
|
||||
ImdbId = emby.ProviderId,
|
||||
AddedAt = emby.AddedAt,
|
||||
Title = emby.Title,
|
||||
EpisodeNumber = episode.EpisodeNumber,
|
||||
SeasonNumber = episode.SeasonNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
src/Ombi.Core/Models/RecentlyAddedMovieModel.cs
Normal file
23
src/Ombi.Core/Models/RecentlyAddedMovieModel.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Core.Models
|
||||
{
|
||||
public class RecentlyAddedMovieModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public string TvDbId { get; set; }
|
||||
public string TheMovieDbId { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string Quality { get; set; }
|
||||
}
|
||||
|
||||
public enum RecentlyAddedType
|
||||
{
|
||||
Plex,
|
||||
Emby
|
||||
}
|
||||
}
|
19
src/Ombi.Core/Models/RecentlyAddedTvModel.cs
Normal file
19
src/Ombi.Core/Models/RecentlyAddedTvModel.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Core.Models
|
||||
{
|
||||
public class RecentlyAddedTvModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } // Series Title
|
||||
public string Overview { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public string TvDbId { get; set; }
|
||||
public string TheMovieDbId { get; set; }
|
||||
public string ReleaseYear { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string Quality { get; set; }
|
||||
public int SeasonNumber { get; set; }
|
||||
public int EpisodeNumber { get; set; }
|
||||
}
|
||||
}
|
23
src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs
Normal file
23
src/Ombi.Core/Models/UI/NewsletterNotificationViewModel.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Core.Models.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// The view model for the notification settings page
|
||||
/// </summary>
|
||||
/// <seealso cref="NewsletterNotificationViewModel" />
|
||||
public class NewsletterNotificationViewModel : NewsletterSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the notification templates.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The notification templates.
|
||||
/// </value>
|
||||
public NotificationTemplates NotificationTemplate { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -79,6 +79,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<ITvSearchEngine, TvSearchEngine>();
|
||||
services.AddTransient<IRuleEvaluator, RuleEvaluator>();
|
||||
services.AddTransient<IMovieSender, MovieSender>();
|
||||
services.AddTransient<IRecentlyAddedEngine, RecentlyAddedEngine>();
|
||||
services.AddTransient<ITvSender, TvSender>();
|
||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||
}
|
||||
|
@ -173,6 +174,7 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IProcessProvider, ProcessProvider>();
|
||||
services.AddTransient<ISickRageSync, SickRageSync>();
|
||||
services.AddTransient<IRefreshMetadata, RefreshMetadata>();
|
||||
services.AddTransient<INewsletterJob, NewsletterJob>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
WelcomeEmail = 8,
|
||||
IssueResolved = 9,
|
||||
IssueComment = 10,
|
||||
Newsletter = 11,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,5 +9,6 @@
|
|||
public const string RequestTv = nameof(RequestTv);
|
||||
public const string RequestMovie = nameof(RequestMovie);
|
||||
public const string Disabled = nameof(Disabled);
|
||||
public const string RecievesNewsletter = nameof(RecievesNewsletter);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ namespace Ombi.Mapping.Profiles
|
|||
CreateMap<TelegramNotificationsViewModel, TelegramSettings>().ReverseMap();
|
||||
CreateMap<UpdateSettingsViewModel, UpdateSettings>().ReverseMap();
|
||||
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
|
||||
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,19 +4,26 @@ using System.Text;
|
|||
|
||||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public class EmailBasicTemplate : IEmailBasicTemplate
|
||||
public class EmailBasicTemplate : TemplateBase, IEmailBasicTemplate
|
||||
{
|
||||
public string TemplateLocation
|
||||
public override string TemplateLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_templateLocation))
|
||||
{
|
||||
#if DEBUG
|
||||
return Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", "BasicTemplate.html");
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates",
|
||||
"BasicTemplate.html");
|
||||
#else
|
||||
return Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates","BasicTemplate.html");
|
||||
#endif
|
||||
}
|
||||
return _templateLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private string _templateLocation;
|
||||
|
||||
private const string SubjectKey = "{@SUBJECT}";
|
||||
private const string BodyKey = "{@BODY}";
|
||||
|
@ -31,7 +38,7 @@ namespace Ombi.Notifications.Templates
|
|||
sb.Replace(BodyKey, body);
|
||||
sb.Replace(DateKey, DateTime.Now.ToString("f"));
|
||||
sb.Replace(Poster, string.IsNullOrEmpty(imgsrc) ? string.Empty : $"<tr><td align=\"center\"><img src=\"{imgsrc}\" alt=\"Poster\" width=\"400px\" text-align=\"center\"/></td></tr>");
|
||||
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? "http://i.imgur.com/qQsN78U.png" : logo);
|
||||
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
|
7
src/Ombi.Notifications.Templates/INewsletterTemplate.cs
Normal file
7
src/Ombi.Notifications.Templates/INewsletterTemplate.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public interface INewsletterTemplate
|
||||
{
|
||||
string LoadTemplate(string subject, string intro, string tableHtml, string logo);
|
||||
}
|
||||
}
|
46
src/Ombi.Notifications.Templates/NewsletterTemplate.cs
Normal file
46
src/Ombi.Notifications.Templates/NewsletterTemplate.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public class NewsletterTemplate : TemplateBase, INewsletterTemplate
|
||||
{
|
||||
public override string TemplateLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_templateLocation))
|
||||
{
|
||||
#if DEBUG
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "bin", "Debug", "netcoreapp2.0", "Templates", "NewsletterTemplate.html");
|
||||
#else
|
||||
_templateLocation = Path.Combine(Directory.GetCurrentDirectory(), "Templates", "NewsletterTemplate.html");
|
||||
#endif
|
||||
}
|
||||
return _templateLocation;
|
||||
}
|
||||
}
|
||||
|
||||
private string _templateLocation;
|
||||
|
||||
private const string SubjectKey = "{@SUBJECT}";
|
||||
private const string DateKey = "{@DATENOW}";
|
||||
private const string Logo = "{@LOGO}";
|
||||
private const string TableLocation = "{@RECENTLYADDED}";
|
||||
private const string IntroText = "{@INTRO}";
|
||||
|
||||
|
||||
public string LoadTemplate(string subject, string intro, string tableHtml, string logo)
|
||||
{
|
||||
var sb = new StringBuilder(File.ReadAllText(TemplateLocation));
|
||||
sb.Replace(SubjectKey, subject);
|
||||
sb.Replace(TableLocation, tableHtml);
|
||||
sb.Replace(IntroText, intro);
|
||||
sb.Replace(DateKey, DateTime.Now.ToString("f"));
|
||||
sb.Replace(Logo, string.IsNullOrEmpty(logo) ? OmbiLogo : logo);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Templates\NewsletterTemplate.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Templates\BasicTemplate.html">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
8
src/Ombi.Notifications.Templates/TemplateBase.cs
Normal file
8
src/Ombi.Notifications.Templates/TemplateBase.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Notifications.Templates
|
||||
{
|
||||
public abstract class TemplateBase
|
||||
{
|
||||
public abstract string TemplateLocation { get; }
|
||||
public virtual string OmbiLogo => "http://i.imgur.com/qQsN78U.png";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<title>Ombi</title>
|
||||
<style media="all" type="text/css">
|
||||
@media all {
|
||||
.btn-primary table td:hover {
|
||||
background-color: #34495e !important;
|
||||
}
|
||||
|
||||
.btn-primary a:hover {
|
||||
background-color: #34495e !important;
|
||||
border-color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media all {
|
||||
.btn-secondary a:hover {
|
||||
border-color: #34495e !important;
|
||||
color: #34495e !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 620px) {
|
||||
table[class=body] h1 {
|
||||
font-size: 28px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] h2 {
|
||||
font-size: 22px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] h3 {
|
||||
font-size: 16px !important;
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] p,
|
||||
table[class=body] ul,
|
||||
table[class=body] ol,
|
||||
table[class=body] td,
|
||||
table[class=body] span,
|
||||
table[class=body] a {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
table[class=body] .wrapper,
|
||||
table[class=body] .article {
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .container {
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .header {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .main {
|
||||
border-left-width: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
border-right-width: 0 !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn table {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .btn a {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .img-responsive {
|
||||
height: auto !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
table[class=body] .alert td {
|
||||
border-radius: 0 !important;
|
||||
padding: 10px !important;
|
||||
}
|
||||
|
||||
table[class=body] .span-2,
|
||||
table[class=body] .span-3 {
|
||||
max-width: none !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
table[class=body] .receipt {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media all {
|
||||
.ExternalClass {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ExternalClass,
|
||||
.ExternalClass p,
|
||||
.ExternalClass span,
|
||||
.ExternalClass font,
|
||||
.ExternalClass td,
|
||||
.ExternalClass div {
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.apple-link a {
|
||||
color: inherit !important;
|
||||
font-family: inherit !important;
|
||||
font-size: inherit !important;
|
||||
font-weight: inherit !important;
|
||||
line-height: inherit !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="" style="font-family: sans-serif; -webkit-font-smoothing: antialiased; font-size: 14px; line-height: 1.4; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; background-color: #f6f6f6; margin: 0; padding: 0;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="body" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background-color: #f6f6f6;" width="100%" bgcolor="#f6f6f6">
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td>
|
||||
<td class="container" style="font-family: sans-serif; font-size: 14px; vertical-align: top; display: block; Margin: 0 auto !important; max-width: 580px; padding: 10px; width: 580px;" width="580" valign="top">
|
||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
||||
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Ombi Recently Added</span>
|
||||
<table class="main" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; background: #fff; border-radius: 3px;" width="100%">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
<td class="wrapper" style="font-family: sans-serif; font-size: 14px; vertical-align: top; box-sizing: border-box; padding: 20px;" valign="top">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<img src="{@LOGO}" width="400px" text-align="center" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top">
|
||||
<br />
|
||||
<br />
|
||||
<p style="font-family: sans-serif; font-size: 20px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Here is a list of Movies and TV Shows that have recently been added!</p>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{@RECENTLYADDED}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- END MAIN CONTENT AREA -->
|
||||
</table>
|
||||
|
||||
<!-- START FOOTER -->
|
||||
<div class="footer" style="clear: both; padding-top: 10px; text-align: center; width: 100%;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;" width="100%">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-top: 10px; padding-bottom: 10px; font-size: 12px; color: #999999; text-align: center;" valign="top" align="center">
|
||||
Powered by <a href="https://github.com/tidusjar/Ombi" style="color: #999999; font-size: 12px; text-align: center; text-decoration: underline;">Ombi</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- END FOOTER -->
|
||||
<!-- END CENTERED WHITE CONTAINER -->
|
||||
</div>
|
||||
</td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;" valign="top"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
|
@ -107,9 +107,13 @@ namespace Ombi.Notifications
|
|||
var body = new BodyBuilder
|
||||
{
|
||||
HtmlBody = model.Message,
|
||||
TextBody = model.Other["PlainTextBody"]
|
||||
};
|
||||
|
||||
if (model.Other.ContainsKey("PlainTextBody"))
|
||||
{
|
||||
body.TextBody = model.Other["PlainTextBody"];
|
||||
}
|
||||
|
||||
var message = new MimeMessage
|
||||
{
|
||||
Body = body.ToMessageBody(),
|
||||
|
|
|
@ -38,6 +38,13 @@ namespace Ombi.Notifications
|
|||
AdditionalInformation = opts?.AdditionalInformation ?? string.Empty;
|
||||
}
|
||||
|
||||
public void SetupNewsletter(CustomizationSettings s, OmbiUser username)
|
||||
{
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = username.UserName;
|
||||
}
|
||||
|
||||
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s)
|
||||
{
|
||||
LoadIssues(opts);
|
||||
|
|
|
@ -17,7 +17,8 @@ namespace Ombi.Schedule
|
|||
public JobSetup(IPlexContentSync plexContentSync, IRadarrSync radarrSync,
|
||||
IOmbiAutomaticUpdater updater, IEmbyContentSync embySync, IPlexUserImporter userImporter,
|
||||
IEmbyUserImporter embyUserImporter, ISonarrSync cache, ICouchPotatoSync cpCache,
|
||||
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh)
|
||||
ISettingsService<JobSettings> jobsettings, ISickRageSync srSync, IRefreshMetadata refresh,
|
||||
INewsletterJob newsletter)
|
||||
{
|
||||
_plexContentSync = plexContentSync;
|
||||
_radarrSync = radarrSync;
|
||||
|
@ -30,6 +31,7 @@ namespace Ombi.Schedule
|
|||
_jobSettings = jobsettings;
|
||||
_srSync = srSync;
|
||||
_refreshMetadata = refresh;
|
||||
_newsletter = newsletter;
|
||||
}
|
||||
|
||||
private readonly IPlexContentSync _plexContentSync;
|
||||
|
@ -43,6 +45,7 @@ namespace Ombi.Schedule
|
|||
private readonly ISickRageSync _srSync;
|
||||
private readonly ISettingsService<JobSettings> _jobSettings;
|
||||
private readonly IRefreshMetadata _refreshMetadata;
|
||||
private readonly INewsletterJob _newsletter;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
|
@ -60,6 +63,7 @@ namespace Ombi.Schedule
|
|||
|
||||
RecurringJob.AddOrUpdate(() => _embyUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||
RecurringJob.AddOrUpdate(() => _plexUserImporter.Start(), JobSettingsHelper.UserImporter(s));
|
||||
BackgroundJob.Enqueue(() => _newsletter.Start());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
46
src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs
Normal file
46
src/Ombi.Schedule/Jobs/Ombi/HtmlTemplateGenerator.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Text;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public abstract class HtmlTemplateGenerator
|
||||
{
|
||||
protected virtual void AddParagraph(StringBuilder stringBuilder, string text, int fontSize = 14, string fontWeight = "normal")
|
||||
{
|
||||
stringBuilder.AppendFormat("<p style=\"font-family: sans-serif; font-size: {1}px; font-weight: {2}; margin: 0; Margin-bottom: 15px;\">{0}</p>", text, fontSize, fontWeight);
|
||||
}
|
||||
|
||||
protected virtual void AddImageInsideTable(StringBuilder sb, string url, int size = 400)
|
||||
{
|
||||
sb.Append("<tr>");
|
||||
sb.Append("<td align=\"center\">");
|
||||
sb.Append($"<img src=\"{url}\" width=\"{size}px\" text-align=\"center\" />");
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
protected virtual void Href(StringBuilder sb, string url)
|
||||
{
|
||||
sb.AppendFormat("<a href=\"{0}\">", url);
|
||||
}
|
||||
|
||||
protected virtual void TableData(StringBuilder sb)
|
||||
{
|
||||
sb.Append(
|
||||
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
||||
}
|
||||
|
||||
protected virtual void EndTag(StringBuilder sb, string tag)
|
||||
{
|
||||
sb.AppendFormat("</{0}>", tag);
|
||||
}
|
||||
|
||||
protected virtual void Header(StringBuilder sb, int size, string text, string fontWeight = "normal")
|
||||
{
|
||||
sb.AppendFormat(
|
||||
"<h{0} style=\"font-family: sans-serif; font-weight: {2}; margin: 0; Margin-bottom: 15px;\">{1}</h{0}>",
|
||||
size, text, fontWeight);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
11
src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs
Normal file
11
src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System.Threading.Tasks;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public interface INewsletterJob : IBaseJob
|
||||
{
|
||||
Task Start();
|
||||
Task Start(NewsletterSettings settings, bool test);
|
||||
}
|
||||
}
|
605
src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs
Normal file
605
src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs
Normal file
|
@ -0,0 +1,605 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Api.TheMovieDb.Models;
|
||||
using Ombi.Api.TvMaze;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Notifications.Templates;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public class NewsletterJob : HtmlTemplateGenerator, INewsletterJob
|
||||
{
|
||||
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)
|
||||
{
|
||||
_plex = plex;
|
||||
_emby = emby;
|
||||
_recentlyAddedLog = addedLog;
|
||||
_movieApi = movieApi;
|
||||
_tvApi = tvApi;
|
||||
_email = email;
|
||||
_customizationSettings = custom;
|
||||
_templateRepo = templateRepo;
|
||||
_emailSettings = emailSettings;
|
||||
_newsletterSettings = newsletter;
|
||||
_userManager = um;
|
||||
_emailSettings.ClearCache();
|
||||
_customizationSettings.ClearCache();
|
||||
_newsletterSettings.ClearCache();
|
||||
}
|
||||
|
||||
private readonly IPlexContentRepository _plex;
|
||||
private readonly IEmbyContentRepository _emby;
|
||||
private readonly IRepository<RecentlyAddedLog> _recentlyAddedLog;
|
||||
private readonly IMovieDbApi _movieApi;
|
||||
private readonly ITvMazeApi _tvApi;
|
||||
private readonly IEmailProvider _email;
|
||||
private readonly ISettingsService<CustomizationSettings> _customizationSettings;
|
||||
private readonly INotificationTemplatesRepository _templateRepo;
|
||||
private readonly ISettingsService<EmailNotificationSettings> _emailSettings;
|
||||
private readonly ISettingsService<NewsletterSettings> _newsletterSettings;
|
||||
private readonly UserManager<OmbiUser> _userManager;
|
||||
|
||||
public async Task Start(NewsletterSettings settings, bool test)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var template = await _templateRepo.GetTemplate(NotificationAgent.Email, NotificationType.Newsletter);
|
||||
if (!template.Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var emailSettings = await _emailSettings.GetSettingsAsync();
|
||||
if (!ValidateConfiguration(emailSettings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var customization = await _customizationSettings.GetSettingsAsync();
|
||||
|
||||
// Get the Content
|
||||
var plexContent = _plex.GetAll().Include(x => x.Episodes);
|
||||
var embyContent = _emby.GetAll().Include(x => x.Episodes);
|
||||
|
||||
var addedLog = _recentlyAddedLog.GetAll();
|
||||
var addedPlexMovieLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
||||
var addedEmbyMoviesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Parent).Select(x => x.ContentId);
|
||||
|
||||
var addedPlexEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Plex && x.ContentType == ContentType.Episode).Select(x => x.ContentId);
|
||||
var addedEmbyEpisodesLogIds = addedLog.Where(x => x.Type == RecentlyAddedType.Emby && x.ContentType == ContentType.Episode).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 plexContentTvToSend = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Show && x.Episodes.Any(e => !addedPlexEpisodesLogIds.Contains(e.Id)));
|
||||
var embyContentTvToSend = embyContent.Where(x => x.Type == EmbyMediaType.Series && x.Episodes.Any(e => !addedEmbyEpisodesLogIds.Contains(e.Id)));
|
||||
|
||||
var plexContentToSend = plexContentMoviesToSend.Union(plexContentTvToSend);
|
||||
var embyContentToSend = embyContentMoviesToSend.Union(embyContentTvToSend);
|
||||
|
||||
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 = plexContent.Where(x => x.Type == PlexMediaTypeEntity.Show).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
var embyt = embyContent.Where(x => x.Type == EmbyMediaType.Series).OrderByDescending(x => x.AddedAt).Take(10);
|
||||
body = await BuildHtml(plexm.Union(plext), embym.Union(embyt));
|
||||
}
|
||||
else
|
||||
{
|
||||
body = await BuildHtml(plexContentToSend, embyContentToSend);
|
||||
if (body.IsNullOrEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!test)
|
||||
{
|
||||
// Get the users to send it to
|
||||
var users = await _userManager.GetUsersInRoleAsync(OmbiRoles.RecievesNewsletter);
|
||||
if (!users.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var emailTasks = new List<Task>();
|
||||
foreach (var user in users)
|
||||
{
|
||||
if (user.Email.IsNullOrEmpty())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var messageContent = ParseTemplate(template, customization, user);
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// Now add all of this to the Recently Added log
|
||||
var recentlyAddedLog = new HashSet<RecentlyAddedLog>();
|
||||
foreach (var p in plexContentMoviesToSend)
|
||||
{
|
||||
if (p.Type == PlexMediaTypeEntity.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = p.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in p.Episodes)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = ep.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var e in embyContentMoviesToSend)
|
||||
{
|
||||
if (e.Type == EmbyMediaType.Movie)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Emby,
|
||||
ContentId = e.Id
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add the episodes
|
||||
foreach (var ep in e.Episodes)
|
||||
{
|
||||
recentlyAddedLog.Add(new RecentlyAddedLog
|
||||
{
|
||||
AddedAt = DateTime.Now,
|
||||
Type = RecentlyAddedType.Plex,
|
||||
ContentId = ep.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
await _recentlyAddedLog.AddRange(recentlyAddedLog);
|
||||
|
||||
await Task.WhenAll(emailTasks.ToArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
var admins = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin);
|
||||
foreach (var a in admins)
|
||||
{
|
||||
var messageContent = ParseTemplate(template, customization, a);
|
||||
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Start()
|
||||
{
|
||||
var newsletterSettings = await _newsletterSettings.GetSettingsAsync();
|
||||
await Start(newsletterSettings, false);
|
||||
}
|
||||
|
||||
private NotificationMessageContent ParseTemplate(NotificationTemplates template, CustomizationSettings settings, OmbiUser username)
|
||||
{
|
||||
var resolver = new NotificationMessageResolver();
|
||||
var curlys = new NotificationMessageCurlys();
|
||||
|
||||
curlys.SetupNewsletter(settings, username);
|
||||
|
||||
return resolver.ParseMessage(template, curlys);
|
||||
}
|
||||
|
||||
private async Task<string> BuildHtml(IQueryable<PlexServerContent> plexContentToSend, IQueryable<EmbyContent> embyContentToSend)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var plexMovies = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Movie);
|
||||
var embyMovies = embyContentToSend.Where(x => x.Type == EmbyMediaType.Movie);
|
||||
if (plexMovies.Any() || embyMovies.Any())
|
||||
{
|
||||
sb.Append("<h1>New Movies:</h1><br /><br />");
|
||||
await ProcessPlexMovies(plexMovies, sb);
|
||||
await ProcessEmbyMovies(embyMovies, sb);
|
||||
}
|
||||
|
||||
var plexTv = plexContentToSend.Where(x => x.Type == PlexMediaTypeEntity.Show);
|
||||
var embyTv = embyContentToSend.Where(x => x.Type == EmbyMediaType.Series);
|
||||
if (plexTv.Any() || embyTv.Any())
|
||||
{
|
||||
sb.Append("<h1>New Episodes:</h1><br /><br />");
|
||||
await ProcessPlexTv(plexTv, sb);
|
||||
await ProcessEmbyMovies(embyTv, sb);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private async Task ProcessPlexMovies(IQueryable<PlexServerContent> plexContentToSend, StringBuilder sb)
|
||||
{
|
||||
sb.Append(
|
||||
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
|
||||
var ordered = plexContentToSend.OrderByDescending(x => x.AddedAt);
|
||||
foreach (var content in ordered)
|
||||
{
|
||||
if (content.TheMovieDbId.IsNullOrEmpty())
|
||||
{
|
||||
// Maybe we should try the ImdbId?
|
||||
if (content.ImdbId.HasValue())
|
||||
{
|
||||
var findResult = await _movieApi.Find(content.ImdbId, ExternalSource.imdb_id);
|
||||
|
||||
var movieId = findResult.movie_results?[0]?.id ?? 0;
|
||||
content.TheMovieDbId = movieId.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
int.TryParse(content.TheMovieDbId, out var movieDbId);
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}");
|
||||
|
||||
sb.Append("<tr>");
|
||||
TableData(sb);
|
||||
|
||||
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
|
||||
Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}");
|
||||
EndTag(sb, "a");
|
||||
|
||||
if (info.Genres.Any())
|
||||
{
|
||||
AddParagraph(sb,
|
||||
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, info.Overview);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
private async Task ProcessEmbyMovies(IQueryable<EmbyContent> embyContent, StringBuilder sb)
|
||||
{
|
||||
sb.Append(
|
||||
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
|
||||
var ordered = embyContent.OrderByDescending(x => x.AddedAt);
|
||||
foreach (var content in ordered)
|
||||
{
|
||||
int.TryParse(content.ProviderId, out var movieDbId);
|
||||
var info = await _movieApi.GetMovieInformationWithExtraInfo(movieDbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
AddImageInsideTable(sb, $"https://image.tmdb.org/t/p/original{info.BackdropPath}");
|
||||
|
||||
sb.Append("<tr>");
|
||||
TableData(sb);
|
||||
|
||||
Href(sb, $"https://www.imdb.com/title/{info.ImdbId}/");
|
||||
Header(sb, 3, $"{info.Title} {info.ReleaseDate ?? string.Empty}");
|
||||
EndTag(sb, "a");
|
||||
|
||||
if (info.Genres.Any())
|
||||
{
|
||||
AddParagraph(sb,
|
||||
$"Genre: {string.Join(", ", info.Genres.Select(x => x.Name.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, info.Overview);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessPlexTv(IQueryable<PlexServerContent> plexContent, StringBuilder sb)
|
||||
{
|
||||
var orderedTv = plexContent.OrderByDescending(x => x.AddedAt);
|
||||
sb.Append(
|
||||
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
|
||||
foreach (var t in orderedTv)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!t.HasTvDb)
|
||||
{
|
||||
// We may need to use themoviedb for the imdbid or their own id to get info
|
||||
if (t.HasTheMovieDb)
|
||||
{
|
||||
int.TryParse(t.TheMovieDbId, out var movieId);
|
||||
var externals = await _movieApi.GetTvExternals(movieId);
|
||||
if (externals == null || externals.tvdb_id <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
t.TvDbId = externals.tvdb_id.ToString();
|
||||
}
|
||||
// WE could check the below but we need to get the moviedb and then perform the above, let the metadata job figure this out.
|
||||
//else if(t.HasImdb)
|
||||
//{
|
||||
// // Check the imdbid
|
||||
// var externals = await _movieApi.Find(t.ImdbId, ExternalSource.imdb_id);
|
||||
// if (externals?.tv_results == null || externals.tv_results.Length <= 0)
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// t.TvDbId = externals.tv_results.FirstOrDefault()..ToString();
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
int.TryParse(t.TvDbId, out var tvdbId);
|
||||
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var banner = info.image?.original;
|
||||
if (!string.IsNullOrEmpty(banner))
|
||||
{
|
||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
||||
}
|
||||
AddImageInsideTable(sb, banner);
|
||||
|
||||
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}/");
|
||||
Header(sb, 3, title);
|
||||
EndTag(sb, "a");
|
||||
|
||||
// Group by the season number
|
||||
var results = t.Episodes?.GroupBy(p => p.SeasonNumber,
|
||||
(key, g) => new
|
||||
{
|
||||
SeasonNumber = key,
|
||||
Episodes = g.ToList()
|
||||
}
|
||||
);
|
||||
|
||||
// Group the episodes
|
||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||
{
|
||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||
var epSb = new StringBuilder();
|
||||
for (var i = 0; i < orderedEpisodes.Count; i++)
|
||||
{
|
||||
var ep = orderedEpisodes[i];
|
||||
if (i < orderedEpisodes.Count - 1)
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber},");
|
||||
}
|
||||
else
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber}");
|
||||
}
|
||||
|
||||
}
|
||||
AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}");
|
||||
}
|
||||
|
||||
if (info.genres.Any())
|
||||
{
|
||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, info.summary);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
sb.Append("</table><br /><br />");
|
||||
|
||||
}
|
||||
|
||||
private async Task ProcessEmbyTv(IQueryable<EmbyContent> plexContent, StringBuilder sb)
|
||||
{
|
||||
var orderedTv = plexContent.OrderByDescending(x => x.AddedAt);
|
||||
sb.Append(
|
||||
"<table border=\"0\" cellpadding=\"0\" align=\"center\" cellspacing=\"0\" style=\"border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;\" width=\"100%\">");
|
||||
foreach (var t in orderedTv)
|
||||
{
|
||||
try
|
||||
{
|
||||
int.TryParse(t.ProviderId, out var tvdbId);
|
||||
var info = await _tvApi.ShowLookupByTheTvDbId(tvdbId);
|
||||
if (info == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var banner = info.image?.original;
|
||||
if (!string.IsNullOrEmpty(banner))
|
||||
{
|
||||
banner = banner.Replace("http", "https"); // Always use the Https banners
|
||||
}
|
||||
AddImageInsideTable(sb, banner);
|
||||
|
||||
sb.Append("<tr>");
|
||||
sb.Append(
|
||||
"<td align=\"center\" style=\"font-family: sans-serif; font-size: 14px; vertical-align: top;\" valign=\"top\">");
|
||||
|
||||
Href(sb, $"https://www.imdb.com/title/{info.externals.imdb}/");
|
||||
Header(sb, 3, t.Title);
|
||||
EndTag(sb, "a");
|
||||
|
||||
// Group by the season number
|
||||
var results = t.Episodes?.GroupBy(p => p.SeasonNumber,
|
||||
(key, g) => new
|
||||
{
|
||||
SeasonNumber = key,
|
||||
Episodes = g.ToList()
|
||||
}
|
||||
);
|
||||
|
||||
// Group the episodes
|
||||
foreach (var epInformation in results.OrderBy(x => x.SeasonNumber))
|
||||
{
|
||||
var orderedEpisodes = epInformation.Episodes.OrderBy(x => x.EpisodeNumber).ToList();
|
||||
var epSb = new StringBuilder();
|
||||
for (var i = 0; i < orderedEpisodes.Count; i++)
|
||||
{
|
||||
var ep = orderedEpisodes[i];
|
||||
if (i < orderedEpisodes.Count - 1)
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber},");
|
||||
}
|
||||
else
|
||||
{
|
||||
epSb.Append($"{ep.EpisodeNumber}");
|
||||
}
|
||||
|
||||
}
|
||||
AddParagraph(sb, $"Season: {epInformation.SeasonNumber}, Episode: {epSb}");
|
||||
}
|
||||
|
||||
if (info.genres.Any())
|
||||
{
|
||||
AddParagraph(sb, $"Genre: {string.Join(", ", info.genres.Select(x => x.ToString()).ToArray())}");
|
||||
}
|
||||
|
||||
AddParagraph(sb, info.summary);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
//Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndLoopHtml(sb);
|
||||
}
|
||||
}
|
||||
sb.Append("</table><br /><br />");
|
||||
}
|
||||
|
||||
private void EndLoopHtml(StringBuilder sb)
|
||||
{
|
||||
//NOTE: BR have to be in TD's as per html spec or it will be put outside of the table...
|
||||
//Source: http://stackoverflow.com/questions/6588638/phantom-br-tag-rendered-by-browsers-prior-to-table-tag
|
||||
sb.Append("<hr />");
|
||||
sb.Append("<br />");
|
||||
sb.Append("<br />");
|
||||
sb.Append("</td>");
|
||||
sb.Append("</tr>");
|
||||
}
|
||||
|
||||
protected bool ValidateConfiguration(EmailNotificationSettings settings)
|
||||
{
|
||||
if (!settings.Enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (settings.Authentication)
|
||||
{
|
||||
if (string.IsNullOrEmpty(settings.Username) || string.IsNullOrEmpty(settings.Password))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (string.IsNullOrEmpty(settings.Host) || string.IsNullOrEmpty(settings.AdminEmail) || string.IsNullOrEmpty(settings.Port.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_plex?.Dispose();
|
||||
_emby?.Dispose();
|
||||
_newsletterSettings?.Dispose();
|
||||
_customizationSettings?.Dispose();
|
||||
_emailSettings.Dispose();
|
||||
_recentlyAddedLog.Dispose();
|
||||
_templateRepo?.Dispose();
|
||||
_userManager?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -220,12 +220,6 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
|
||||
|
||||
private async Task StartEmby()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
private bool _disposed;
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
|
@ -236,6 +230,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
_plexRepo?.Dispose();
|
||||
_embyRepo?.Dispose();
|
||||
_plexSettings?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
|
|
@ -46,7 +46,8 @@ namespace Ombi.Schedule.Processor
|
|||
if (masterBranch)
|
||||
{
|
||||
latestRelease = doc.DocumentNode.Descendants("h2")
|
||||
.FirstOrDefault(x => x.InnerText != "(unreleased)");
|
||||
.FirstOrDefault(x => x.InnerText == "(unreleased)");
|
||||
// TODO: Change this to InnterText != "(unreleased)" once we go live and it's not a prerelease
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace Ombi.Settings.Settings.Models
|
|||
|
||||
public string PresetThemeName { get; set; }
|
||||
public string PresetThemeContent { get; set; }
|
||||
public bool RecentlyAddedPage { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string PresetThemeVersion
|
||||
|
|
|
@ -11,5 +11,6 @@
|
|||
public string UserImporter { get; set; }
|
||||
public string SickRageSync { get; set; }
|
||||
public string RefreshMetadata { get; set; }
|
||||
public string Newsletter { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using Ombi.Helpers;
|
||||
using System;
|
||||
using Ombi.Helpers;
|
||||
|
||||
namespace Ombi.Settings.Settings.Models
|
||||
{
|
||||
|
@ -35,6 +36,10 @@ namespace Ombi.Settings.Settings.Models
|
|||
{
|
||||
return Get(s.UserImporter, Cron.Daily());
|
||||
}
|
||||
public static string Newsletter(JobSettings s)
|
||||
{
|
||||
return Get(s.Newsletter, Cron.Weekly(DayOfWeek.Friday, 12));
|
||||
}
|
||||
public static string SickRageSync(JobSettings s)
|
||||
{
|
||||
return Get(s.SickRageSync, Cron.Hourly(35));
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
namespace Ombi.Settings.Settings.Models.Notifications
|
||||
{
|
||||
public class NewsletterSettings : Settings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
|
@ -41,5 +41,6 @@ namespace Ombi.Store.Context
|
|||
DbSet<SickRageCache> SickRageCache { get; set; }
|
||||
DbSet<SickRageEpisodeCache> SickRageEpisodeCache { get; set; }
|
||||
DbSet<RequestLog> RequestLogs { get; set; }
|
||||
DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Helpers;
|
||||
|
@ -37,6 +38,7 @@ namespace Ombi.Store.Context
|
|||
public DbSet<IssueCategory> IssueCategories { get; set; }
|
||||
public DbSet<IssueComments> IssueComments { get; set; }
|
||||
public DbSet<RequestLog> RequestLogs { get; set; }
|
||||
public DbSet<RecentlyAddedLog> RecentlyAddedLogs { get; set; }
|
||||
|
||||
|
||||
public DbSet<Audit> Audit { get; set; }
|
||||
|
@ -113,6 +115,12 @@ namespace Ombi.Store.Context
|
|||
// VACUUM;
|
||||
Database.ExecuteSqlCommand("VACUUM;");
|
||||
|
||||
// Make sure we have the roles
|
||||
var roles = Roles.Where(x => x.Name == OmbiRoles.RecievesNewsletter);
|
||||
if (!roles.Any())
|
||||
{
|
||||
Roles.Add(new IdentityRole(OmbiRoles.RecievesNewsletter));
|
||||
}
|
||||
//Check if templates exist
|
||||
var templates = NotificationTemplates.ToList();
|
||||
|
||||
|
@ -218,6 +226,16 @@ namespace Ombi.Store.Context
|
|||
break;
|
||||
case NotificationType.AdminNote:
|
||||
continue;
|
||||
case NotificationType.Newsletter:
|
||||
notificationToAdd = new NotificationTemplates
|
||||
{
|
||||
NotificationType = notificationType,
|
||||
Message = "Here is a list of Movies and TV Shows that have recently been added!",
|
||||
Subject = "{ApplicationName}: Recently Added Content!",
|
||||
Agent = agent,
|
||||
Enabled = true,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
|
|
@ -56,6 +56,15 @@ namespace Ombi.Store.Entities
|
|||
public int Key { get; set; }
|
||||
public DateTime AddedAt { get; set; }
|
||||
public string Quality { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public bool HasImdb => !string.IsNullOrEmpty(ImdbId);
|
||||
|
||||
[NotMapped]
|
||||
public bool HasTvDb => !string.IsNullOrEmpty(TvDbId);
|
||||
|
||||
[NotMapped]
|
||||
public bool HasTheMovieDb => !string.IsNullOrEmpty(TheMovieDbId);
|
||||
}
|
||||
|
||||
[Table("PlexSeasonsContent")]
|
||||
|
|
26
src/Ombi.Store/Entities/RecentlyAddedLog.cs
Normal file
26
src/Ombi.Store/Entities/RecentlyAddedLog.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities
|
||||
{
|
||||
[Table("RecentlyAddedLog")]
|
||||
public class RecentlyAddedLog : Entity
|
||||
{
|
||||
public RecentlyAddedType Type { get; set; }
|
||||
public ContentType ContentType { get; set; }
|
||||
public int ContentId { get; set; } // This is dependant on the type
|
||||
public DateTime AddedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum RecentlyAddedType
|
||||
{
|
||||
Plex = 0,
|
||||
Emby = 1
|
||||
}
|
||||
|
||||
public enum ContentType
|
||||
{
|
||||
Parent = 0,
|
||||
Episode = 1
|
||||
}
|
||||
}
|
936
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.Designer.cs
generated
Normal file
936
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.Designer.cs
generated
Normal file
|
@ -0,0 +1,936 @@
|
|||
// <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("20180322204610_RecentlyAddedLog")]
|
||||
partial class RecentlyAddedLog
|
||||
{
|
||||
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>("ProviderId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
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>("ParentId");
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
34
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.cs
Normal file
34
src/Ombi.Store/Migrations/20180322204610_RecentlyAddedLog.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
public partial class RecentlyAddedLog : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecentlyAddedLog",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
AddedAt = table.Column<DateTime>(nullable: false),
|
||||
ContentId = table.Column<int>(nullable: false),
|
||||
ContentType = table.Column<int>(nullable: false),
|
||||
Type = table.Column<int>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecentlyAddedLog", x => x.Id);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecentlyAddedLog");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,7 +20,7 @@ namespace Ombi.Store.Migrations
|
|||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
.HasAnnotation("ProductVersion", "2.0.2-rtm-10011");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
|
@ -430,6 +430,24 @@ namespace Ombi.Store.Migrations
|
|||
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")
|
||||
|
|
|
@ -45,9 +45,9 @@ namespace Ombi.Store.Repository
|
|||
|
||||
private IOmbiContext Db { get; }
|
||||
|
||||
public async Task<IEnumerable<EmbyContent>> GetAll()
|
||||
public IQueryable<EmbyContent> GetAll()
|
||||
{
|
||||
return await Db.EmbyContent.ToListAsync();
|
||||
return Db.EmbyContent.AsQueryable();
|
||||
}
|
||||
|
||||
public async Task AddRange(IEnumerable<EmbyContent> content)
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace Ombi.Store.Repository
|
|||
Task<bool> ContentExists(string providerId);
|
||||
IQueryable<EmbyContent> Get();
|
||||
Task<EmbyContent> Get(string providerId);
|
||||
Task<IEnumerable<EmbyContent>> GetAll();
|
||||
IQueryable<EmbyContent> GetAll();
|
||||
Task<EmbyContent> GetByEmbyId(string embyId);
|
||||
Task Update(EmbyContent existingContent);
|
||||
IQueryable<EmbyEpisode> GetAllEpisodes();
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Helpers;
|
||||
|
@ -6,11 +7,11 @@ using Ombi.Store.Entities;
|
|||
|
||||
namespace Ombi.Store.Repository
|
||||
{
|
||||
public interface INotificationTemplatesRepository
|
||||
public interface INotificationTemplatesRepository : IDisposable
|
||||
{
|
||||
IQueryable<NotificationTemplates> All();
|
||||
Task<IEnumerable<NotificationTemplates>> GetAllTemplates();
|
||||
Task<IEnumerable<NotificationTemplates>> GetAllTemplates(NotificationAgent agent);
|
||||
IQueryable<NotificationTemplates> GetAllTemplates();
|
||||
IQueryable<NotificationTemplates> GetAllTemplates(NotificationAgent agent);
|
||||
Task<NotificationTemplates> Insert(NotificationTemplates entity);
|
||||
Task Update(NotificationTemplates template);
|
||||
Task UpdateRange(IEnumerable<NotificationTemplates> template);
|
||||
|
|
|
@ -23,14 +23,14 @@ namespace Ombi.Store.Repository
|
|||
return Db.NotificationTemplates.AsQueryable();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NotificationTemplates>> GetAllTemplates()
|
||||
public IQueryable<NotificationTemplates> GetAllTemplates()
|
||||
{
|
||||
return await Db.NotificationTemplates.ToListAsync();
|
||||
return Db.NotificationTemplates;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<NotificationTemplates>> GetAllTemplates(NotificationAgent agent)
|
||||
public IQueryable<NotificationTemplates> GetAllTemplates(NotificationAgent agent)
|
||||
{
|
||||
return await Db.NotificationTemplates.Where(x => x.Agent == agent).ToListAsync();
|
||||
return Db.NotificationTemplates.Where(x => x.Agent == agent);
|
||||
}
|
||||
|
||||
public async Task<NotificationTemplates> GetTemplate(NotificationAgent agent, NotificationType type)
|
||||
|
@ -40,6 +40,11 @@ namespace Ombi.Store.Repository
|
|||
|
||||
public async Task Update(NotificationTemplates template)
|
||||
{
|
||||
if (Db.Entry(template).State == EntityState.Detached)
|
||||
{
|
||||
Db.Attach(template);
|
||||
Db.Entry(template).State = EntityState.Modified;
|
||||
}
|
||||
await Db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
|
@ -60,5 +65,26 @@ namespace Ombi.Store.Repository
|
|||
await Db.SaveChangesAsync().ConfigureAwait(false);
|
||||
return settings.Entity;
|
||||
}
|
||||
|
||||
private bool _disposed;
|
||||
// Protected implementation of Dispose pattern.
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
Db?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -34,6 +34,14 @@
|
|||
<i class="fa fa-th-list"></i> {{ 'NavigationBar.Requests' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div *ngIf="customizationSettings">
|
||||
<ul *ngIf="customizationSettings.recentlyAddedPage" class="nav navbar-nav">
|
||||
<li id="RecentlyAdded" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/recentlyadded']">
|
||||
<i class="fa fa-check"></i> {{ 'NavigationBar.RecentlyAdded' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul *ngIf="issuesEnabled" class="nav navbar-nav">
|
||||
<li id="Issues" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/issues']">
|
||||
|
@ -46,6 +54,7 @@
|
|||
<i class="fa fa-user"></i> {{ 'NavigationBar.UserManagement' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul *ngIf="hasRole('Admin') || hasRole('PowerUser')" class="nav navbar-nav">
|
||||
<li>
|
||||
<a href="https://www.paypal.me/PlexRequestsNet" target="_blank" pTooltip="{{ 'NavigationBar.DonateTooltip' | translate }}">
|
||||
|
@ -84,7 +93,7 @@
|
|||
<a [routerLink]="['/usermanagement/updatedetails']">
|
||||
<i class="fa fa-key"></i>{{ 'NavigationBar.UpdateDetails' | translate }}</a>
|
||||
</li>
|
||||
<li *ngIf="customizationSettings.mobile" [routerLinkActive]="['active']">
|
||||
<li *ngIf="customizationSettings?.mobile" [routerLinkActive]="['active']">
|
||||
<a href="#" (click)="openMobileApp($event)">
|
||||
<i class="fa fa-mobile"></i>{{ 'NavigationBar.OpenMobileApp' | translate }}</a>
|
||||
</li>
|
||||
|
|
|
@ -52,6 +52,7 @@ const routes: Routes = [
|
|||
{ loadChildren: "./usermanagement/usermanagement.module#UserManagementModule", path: "usermanagement" },
|
||||
{ loadChildren: "./requests/requests.module#RequestsModule", path: "requests" },
|
||||
{ loadChildren: "./search/search.module#SearchModule", path: "search" },
|
||||
{ loadChildren: "./recentlyAdded/recentlyAdded.module#RecentlyAddedModule", path: "recentlyadded" },
|
||||
];
|
||||
|
||||
// AoT requires an exported function for factories
|
||||
|
|
|
@ -46,6 +46,7 @@ export enum NotificationType {
|
|||
WelcomeEmail = 8,
|
||||
IssueResolved = 9,
|
||||
IssueComment = 10,
|
||||
Newsletter = 11,
|
||||
}
|
||||
|
||||
export interface IDiscordNotifcationSettings extends INotificationSettings {
|
||||
|
@ -54,6 +55,10 @@ export interface IDiscordNotifcationSettings extends INotificationSettings {
|
|||
notificationTemplates: INotificationTemplates[];
|
||||
}
|
||||
|
||||
export interface INewsletterNotificationSettings extends INotificationSettings {
|
||||
notificationTemplate: INotificationTemplates;
|
||||
}
|
||||
|
||||
export interface ITelegramNotifcationSettings extends INotificationSettings {
|
||||
botApi: string;
|
||||
chatId: string;
|
||||
|
|
29
src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts
Normal file
29
src/Ombi/ClientApp/app/interfaces/IRecentlyAdded.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
export interface IRecentlyAddedMovies {
|
||||
id: number;
|
||||
title: string;
|
||||
overview: string;
|
||||
imdbId: string;
|
||||
theMovieDbId: string;
|
||||
releaseYear: string;
|
||||
addedAt: Date;
|
||||
quality: string;
|
||||
|
||||
// For UI only
|
||||
posterPath: string;
|
||||
}
|
||||
|
||||
export interface IRecentlyAddedTvShows extends IRecentlyAddedMovies {
|
||||
seasonNumber: number;
|
||||
episodeNumber: number;
|
||||
tvDbId: number;
|
||||
}
|
||||
|
||||
export interface IRecentlyAddedRangeModel {
|
||||
from: Date;
|
||||
to: Date;
|
||||
}
|
||||
|
||||
export enum RecentlyAddedType {
|
||||
Plex,
|
||||
Emby,
|
||||
}
|
|
@ -107,6 +107,7 @@ export interface ICustomizationSettings extends ISettings {
|
|||
presetThemeContent: string;
|
||||
presetThemeDisplayName: string;
|
||||
presetThemeVersion: string;
|
||||
recentlyAddedPage: boolean;
|
||||
}
|
||||
|
||||
export interface IThemes {
|
||||
|
@ -126,6 +127,7 @@ export interface IJobSettings {
|
|||
userImporter: string;
|
||||
sickRageSync: string;
|
||||
refreshMetadata: string;
|
||||
newsletter: string;
|
||||
}
|
||||
|
||||
export interface IIssueSettings extends ISettings {
|
||||
|
|
|
@ -13,3 +13,4 @@ export * from "./ISettings";
|
|||
export * from "./ISonarr";
|
||||
export * from "./IUser";
|
||||
export * from "./IIssues";
|
||||
export * from "./IRecentlyAdded";
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<h1>Recently Added</h1>
|
||||
<input type="checkbox" [(ngModel)]="groupTv" (click)="change()" />
|
||||
<hr />
|
||||
<p-calendar [(ngModel)]="range" showButtonBar="true" selectionMode="range" (onClose)="close()"></p-calendar>
|
||||
<hr />
|
||||
<style>
|
||||
.img-conatiner {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Bottom left text */
|
||||
.bottom-left {
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<ngu-carousel [inputs]="carouselTile">
|
||||
<ngu-tile NguCarouselItem *ngFor="let movie of movies">
|
||||
<div class="img-container">
|
||||
<img class="img-responsive poster" src="{{movie.posterPath}}" style="width: 300px" alt="poster">
|
||||
<div class="bottom-left"> {{movie.title}}</div>
|
||||
</div>
|
||||
|
||||
|
||||
</ngu-tile>
|
||||
|
||||
<button NguCarouselPrev class='leftRs'><i class="fa fa-arrow-left"></i></button>
|
||||
<button NguCarouselNext class='rightRs'><i class="fa fa-arrow-right"></i></button>
|
||||
</ngu-carousel>
|
||||
|
||||
|
||||
|
||||
<hr/>
|
||||
|
||||
<ngu-carousel [inputs]="carouselTile">
|
||||
<ngu-tile NguCarouselItem *ngFor="let t of tv">
|
||||
<img class="img-responsive poster" src="{{t.posterPath}}" style="width: 300px" alt="poster">
|
||||
<b>{{t.title}}</b>
|
||||
<br>
|
||||
<b>Season: {{t.seasonNumber}}</b>
|
||||
<br>
|
||||
<b>Episode: {{t.episodeNumber}}</b>
|
||||
</ngu-tile>
|
||||
|
||||
<button NguCarouselPrev class='leftRs'><i class="fa fa-arrow-left"></i></button>
|
||||
<button NguCarouselNext class='rightRs'><i class="fa fa-arrow-right"></i></button>
|
||||
</ngu-carousel>
|
127
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts
Normal file
127
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.component.ts
Normal file
|
@ -0,0 +1,127 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { NguCarousel } from "@ngu/carousel";
|
||||
|
||||
import { ImageService, RecentlyAddedService } from "../services";
|
||||
import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces";
|
||||
|
||||
@Component({
|
||||
templateUrl: "recentlyAdded.component.html",
|
||||
styles: [`
|
||||
.leftRs {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3);
|
||||
border-radius: 100%;
|
||||
left: 0;
|
||||
background: #df691a;
|
||||
}
|
||||
|
||||
.rightRs {
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
box-shadow: 1px 2px 10px -1px rgba(0, 0, 0, .3);
|
||||
border-radius: 100%;
|
||||
right: 0;
|
||||
background: #df691a;
|
||||
}
|
||||
`],
|
||||
})
|
||||
|
||||
export class RecentlyAddedComponent implements OnInit {
|
||||
public movies: IRecentlyAddedMovies[];
|
||||
public tv: IRecentlyAddedTvShows[];
|
||||
public range: Date[];
|
||||
|
||||
public groupTv: boolean = false;
|
||||
|
||||
// https://github.com/sheikalthaf/ngu-carousel
|
||||
public carouselTile: NguCarousel;
|
||||
|
||||
constructor(private recentlyAddedService: RecentlyAddedService,
|
||||
private imageService: ImageService) {}
|
||||
|
||||
public ngOnInit() {
|
||||
this.getMovies();
|
||||
this.getShows();
|
||||
|
||||
this.carouselTile = {
|
||||
grid: {xs: 2, sm: 3, md: 3, lg: 5, all: 0},
|
||||
slide: 2,
|
||||
speed: 400,
|
||||
animation: "lazy",
|
||||
point: {
|
||||
visible: true,
|
||||
},
|
||||
load: 2,
|
||||
touch: true,
|
||||
easing: "ease",
|
||||
};
|
||||
}
|
||||
|
||||
public close() {
|
||||
if(this.range.length < 2) {
|
||||
return;
|
||||
}
|
||||
if(!this.range[1]) {
|
||||
// If we do not have a second date then just set it to now
|
||||
this.range[1] = new Date();
|
||||
}
|
||||
this.getMovies();
|
||||
}
|
||||
|
||||
public change() {
|
||||
this.getShows();
|
||||
}
|
||||
|
||||
private getShows() {
|
||||
if(this.groupTv) {
|
||||
this.recentlyAddedService.getRecentlyAddedTvGrouped().subscribe(x => {
|
||||
this.tv = x;
|
||||
|
||||
this.tv.forEach((t) => {
|
||||
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
|
||||
t.posterPath = p;
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this.recentlyAddedService.getRecentlyAddedTv().subscribe(x => {
|
||||
this.tv = x;
|
||||
|
||||
this.tv.forEach((t) => {
|
||||
this.imageService.getTvPoster(t.tvDbId).subscribe(p => {
|
||||
t.posterPath = p;
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getMovies() {
|
||||
this.recentlyAddedService.getRecentlyAddedMovies().subscribe(x => {
|
||||
this.movies = x;
|
||||
|
||||
this.movies.forEach((movie) => {
|
||||
if(movie.theMovieDbId) {
|
||||
this.imageService.getMoviePoster(movie.theMovieDbId).subscribe(p => {
|
||||
movie.posterPath = p;
|
||||
});
|
||||
} else if(movie.imdbId) {
|
||||
this.imageService.getMoviePoster(movie.imdbId).subscribe(p => {
|
||||
movie.posterPath = p;
|
||||
});
|
||||
} else {
|
||||
movie.posterPath = "";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
47
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.module.ts
Normal file
47
src/Ombi/ClientApp/app/recentlyAdded/recentlyAdded.module.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { OrderModule } from "ngx-order-pipe";
|
||||
import { CalendarModule, PaginatorModule, SharedModule, TabViewModule } from "primeng/primeng";
|
||||
|
||||
import { IdentityService, ImageService, RecentlyAddedService } from "../services";
|
||||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
import { SharedModule as OmbiShared } from "../shared/shared.module";
|
||||
|
||||
import { RecentlyAddedComponent } from "./recentlyAdded.component";
|
||||
|
||||
import { NguCarouselModule } from "@ngu/carousel";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", component: RecentlyAddedComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
NgbModule.forRoot(),
|
||||
SharedModule,
|
||||
OrderModule,
|
||||
OmbiShared,
|
||||
PaginatorModule,
|
||||
TabViewModule,
|
||||
CalendarModule,
|
||||
NguCarouselModule,
|
||||
],
|
||||
declarations: [
|
||||
RecentlyAddedComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
],
|
||||
providers: [
|
||||
IdentityService,
|
||||
RecentlyAddedService,
|
||||
ImageService,
|
||||
],
|
||||
|
||||
})
|
||||
export class RecentlyAddedModule { }
|
|
@ -12,6 +12,7 @@ import {
|
|||
IEmailNotificationSettings,
|
||||
IEmbyServer,
|
||||
IMattermostNotifcationSettings,
|
||||
INewsletterNotificationSettings,
|
||||
IPlexServer,
|
||||
IPushbulletNotificationSettings,
|
||||
IPushoverNotificationSettings,
|
||||
|
@ -78,4 +79,7 @@ export class TesterService extends ServiceHelpers {
|
|||
public sickrageTest(settings: ISickRageSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}sickrage`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
public newsletterTest(settings: INewsletterNotificationSettings): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}newsletter`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,4 +20,10 @@ export class ImageService extends ServiceHelpers {
|
|||
public getTvBanner(tvdbid: number): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}tv/${tvdbid}`, {headers: this.headers});
|
||||
}
|
||||
public getMoviePoster(themoviedbid: string): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}poster/movie/${themoviedbid}`, {headers: this.headers});
|
||||
}
|
||||
public getTvPoster(tvdbid: number): Observable<string> {
|
||||
return this.http.get<string>(`${this.url}poster/tv/${tvdbid}`, {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from "./applications";
|
||||
export * from "./applications";
|
||||
export * from "./helpers";
|
||||
export * from "./identity.service";
|
||||
export * from "./image.service";
|
||||
|
@ -13,3 +13,4 @@ export * from "./job.service";
|
|||
export * from "./issues.service";
|
||||
export * from "./mobile.service";
|
||||
export * from "./notificationMessage.service";
|
||||
export * from "./recentlyAdded.service";
|
||||
|
|
25
src/Ombi/ClientApp/app/services/recentlyAdded.service.ts
Normal file
25
src/Ombi/ClientApp/app/services/recentlyAdded.service.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Observable } from "rxjs/Rx";
|
||||
|
||||
import { IRecentlyAddedMovies, IRecentlyAddedTvShows } from "./../interfaces";
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
||||
@Injectable()
|
||||
export class RecentlyAddedService extends ServiceHelpers {
|
||||
constructor(http: HttpClient, public platformLocation: PlatformLocation) {
|
||||
super(http, "/api/v1/recentlyadded/", platformLocation);
|
||||
}
|
||||
public getRecentlyAddedMovies(): Observable<IRecentlyAddedMovies[]> {
|
||||
return this.http.get<IRecentlyAddedMovies[]>(`${this.url}movies/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getRecentlyAddedTv(): Observable<IRecentlyAddedTvShows[]> {
|
||||
return this.http.get<IRecentlyAddedTvShows[]>(`${this. url}tv/`, {headers: this.headers});
|
||||
}
|
||||
public getRecentlyAddedTvGrouped(): Observable<IRecentlyAddedTvShows[]> {
|
||||
return this.http.get<IRecentlyAddedTvShows[]>(`${this.url}tv/grouped`, {headers: this.headers});
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ import {
|
|||
ILandingPageSettings,
|
||||
IMattermostNotifcationSettings,
|
||||
IMobileNotifcationSettings,
|
||||
INewsletterNotificationSettings,
|
||||
IOmbiSettings,
|
||||
IPlexSettings,
|
||||
IPushbulletNotificationSettings,
|
||||
|
@ -265,4 +266,17 @@ export class SettingsService extends ServiceHelpers {
|
|||
return this.http
|
||||
.post<boolean>(`${this.url}/issues`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getNewsletterSettings(): Observable<INewsletterNotificationSettings> {
|
||||
return this.http.get<INewsletterNotificationSettings>(`${this.url}/notifications/newsletter`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public updateNewsletterDatabase(): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}/notifications/newsletterdatabase`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveNewsletterSettings(settings: INewsletterNotificationSettings): Observable<boolean> {
|
||||
return this.http
|
||||
.post<boolean>(`${this.url}/notifications/newsletter`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,13 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enable" [(ngModel)]="settings.recentlyAddedPage" [checked]="settings.recentlyAddedPage">
|
||||
<label for="enable">Enable Recently Added Page</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="logo" class="control-label">Custom Logo</label>
|
||||
<div>
|
||||
|
|
|
@ -79,6 +79,14 @@
|
|||
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('refreshMetadata')?.value)">Test</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="userImporter" class="control-label">Newsletter</label>
|
||||
<input type="text" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('newsletter').hasError('required')}" id="newsletter" name="newsletter" formControlName="newsletter">
|
||||
<small *ngIf="form.get('newsletter').hasError('required')" class="error-text">The Newsletter is required</small>
|
||||
<button type="button" class="btn btn-sm btn-primary-outline" (click)="testCron(form.get('newsletter')?.value)">Test</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</fieldset>
|
||||
|
|
|
@ -32,6 +32,7 @@ export class JobsComponent implements OnInit {
|
|||
radarrSync: [x.sonarrSync, Validators.required],
|
||||
sickRageSync: [x.sickRageSync, Validators.required],
|
||||
refreshMetadata: [x.refreshMetadata, Validators.required],
|
||||
newsletter: [x.newsletter, Validators.required],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<settings-menu></settings-menu>
|
||||
|
||||
<div *ngIf="settings">
|
||||
<fieldset>
|
||||
<legend>Newsletter</legend>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enabled" [(ngModel)]="settings.enabled" ng-checked="settings.enabled"><label for="enabled">Enable</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Subject</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" [(ngModel)]="settings.notificationTemplate.subject" value="{{settings.notificationTemplate.subject}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Message</label>
|
||||
<div>
|
||||
<textarea type="text" class="form-control form-control-custom" [(ngModel)]="settings.notificationTemplate.message" value="{{settings.notificationTemplate.message}}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button type="submit" id="save" (click)="onSubmit()" class="btn btn-primary-outline">Submit</button>
|
||||
<button type="button" (click)="test()" class="btn btn-danger-outline">Test</button>
|
||||
<button type="button" (click)="updateDatabase()" class="btn btn-info-outline" tooltipPosition="top" pTooltip="I recommend running this with a fresh Ombi install, this will set all the current *found* content to have been sent via Newsletter,
|
||||
if you do not do this then everything that Ombi has found in your libraries will go out on the first email!">Update Database</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
|
@ -0,0 +1,45 @@
|
|||
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { INewsletterNotificationSettings, NotificationType } from "../../interfaces";
|
||||
import { NotificationService } from "../../services";
|
||||
import { SettingsService } from "../../services";
|
||||
import { TesterService } from "./../../services/applications/tester.service";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./newsletter.component.html",
|
||||
})
|
||||
export class NewsletterComponent implements OnInit {
|
||||
|
||||
public NotificationType = NotificationType;
|
||||
public settings: INewsletterNotificationSettings;
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private testService: TesterService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.settingsService.getNewsletterSettings().subscribe(x => {
|
||||
this.settings = x;
|
||||
});
|
||||
}
|
||||
|
||||
public updateDatabase() {
|
||||
this.settingsService.updateNewsletterDatabase().subscribe();
|
||||
}
|
||||
|
||||
public test() {
|
||||
this.testService.newsletterTest(this.settings).subscribe();
|
||||
}
|
||||
|
||||
public onSubmit() {
|
||||
this.settingsService.saveNewsletterSettings(this.settings).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully saved the Newsletter settings");
|
||||
} else {
|
||||
this.notificationService.error("There was an error when saving the Newsletter settings");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ import { DiscordComponent } from "./notifications/discord.component";
|
|||
import { EmailNotificationComponent } from "./notifications/emailnotification.component";
|
||||
import { MattermostComponent } from "./notifications/mattermost.component";
|
||||
import { MobileComponent } from "./notifications/mobile.component";
|
||||
import { NewsletterComponent } from "./notifications/newsletter.component";
|
||||
import { NotificationTemplate } from "./notifications/notificationtemplate.component";
|
||||
import { PushbulletComponent } from "./notifications/pushbullet.component";
|
||||
import { PushoverComponent } from "./notifications/pushover.component";
|
||||
|
@ -69,6 +70,7 @@ const routes: Routes = [
|
|||
{ path: "Authentication", component: AuthenticationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Mobile", component: MobileComponent, canActivate: [AuthGuard] },
|
||||
{ path: "MassEmail", component: MassEmailComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Newsletter", component: NewsletterComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -119,6 +121,7 @@ const routes: Routes = [
|
|||
AuthenticationComponent,
|
||||
MobileComponent,
|
||||
MassEmailComponent,
|
||||
NewsletterComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
<ul class="dropdown-menu">
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Email']">Email</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/MassEmail']">Mass Email</a></li>
|
||||
<!--<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li>-->
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Newsletter']">Newsletter</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Discord']">Discord</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Slack']">Slack</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Pushbullet']">Pushbullet</a></li>
|
||||
|
|
|
@ -10,12 +10,14 @@ using Ombi.Api.Radarr;
|
|||
using Ombi.Api.SickRage;
|
||||
using Ombi.Api.Sonarr;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Core.Models.UI;
|
||||
using Ombi.Core.Notifications;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Notifications;
|
||||
using Ombi.Notifications.Agents;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Schedule.Jobs.Ombi;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
using Ombi.Settings.Settings.Models.Notifications;
|
||||
|
||||
|
@ -35,7 +37,7 @@ namespace Ombi.Controllers.External
|
|||
public TesterController(INotificationService service, IDiscordNotification notification, IEmailNotification emailN,
|
||||
IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm,
|
||||
IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider,
|
||||
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi)
|
||||
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter)
|
||||
{
|
||||
Service = service;
|
||||
DiscordNotification = notification;
|
||||
|
@ -53,6 +55,7 @@ namespace Ombi.Controllers.External
|
|||
CouchPotatoApi = cpApi;
|
||||
TelegramNotification = telegram;
|
||||
SickRageApi = srApi;
|
||||
Newsletter = newsletter;
|
||||
}
|
||||
|
||||
private INotificationService Service { get; }
|
||||
|
@ -71,6 +74,7 @@ namespace Ombi.Controllers.External
|
|||
private IEmailProvider EmailProvider { get; }
|
||||
private ITelegramNotification TelegramNotification { get; }
|
||||
private ISickRageApi SickRageApi { get; }
|
||||
private INewsletterJob Newsletter { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
@ -368,5 +372,21 @@ namespace Ombi.Controllers.External
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("newsletter")]
|
||||
public async Task<bool> NewsletterTest([FromBody] NewsletterNotificationViewModel settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
settings.Enabled = true;
|
||||
await Newsletter.Start(settings, true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.LogError(LoggingEvents.Api, e, "Could not test Newsletter");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Ombi.Api.TheMovieDb;
|
||||
using Ombi.Config;
|
||||
using Ombi.Helpers;
|
||||
|
||||
|
@ -17,16 +18,16 @@ namespace Ombi.Controllers
|
|||
[Produces("application/json")]
|
||||
public class ImagesController : Controller
|
||||
{
|
||||
public ImagesController(IFanartTvApi api, IApplicationConfigRepository config,
|
||||
public ImagesController(IFanartTvApi fanartTvApi, IApplicationConfigRepository config,
|
||||
IOptions<LandingPageBackground> options, ICacheService c)
|
||||
{
|
||||
Api = api;
|
||||
FanartTvApi = fanartTvApi;
|
||||
Config = config;
|
||||
Options = options.Value;
|
||||
_cache = c;
|
||||
}
|
||||
|
||||
private IFanartTvApi Api { get; }
|
||||
private IFanartTvApi FanartTvApi { get; }
|
||||
private IApplicationConfigRepository Config { get; }
|
||||
private LandingPageBackground Options { get; }
|
||||
private readonly ICacheService _cache;
|
||||
|
@ -36,15 +37,83 @@ namespace Ombi.Controllers
|
|||
{
|
||||
var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1));
|
||||
|
||||
var images = await Api.GetTvImages(tvdbid, key.Value);
|
||||
var images = await FanartTvApi.GetTvImages(tvdbid, key.Value);
|
||||
if (images == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (images.tvbanner != null)
|
||||
{
|
||||
return images.tvbanner.FirstOrDefault()?.url ?? string.Empty;
|
||||
}
|
||||
if (images.showbackground != null)
|
||||
var enImage = images.tvbanner.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
if (enImage == null)
|
||||
{
|
||||
return images.showbackground.FirstOrDefault()?.url ?? string.Empty;
|
||||
return images.tvbanner.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
if (images.seasonposter != null)
|
||||
{
|
||||
return images.seasonposter.FirstOrDefault()?.url ?? string.Empty;
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
[HttpGet("poster/movie/{movieDbId}")]
|
||||
public async Task<string> GetMoviePoster(string movieDbId)
|
||||
{
|
||||
var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1));
|
||||
|
||||
var images = await FanartTvApi.GetMovieImages(movieDbId, key.Value);
|
||||
|
||||
if (images == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (images.movieposter?.Any() ?? false)
|
||||
{
|
||||
var enImage = images.movieposter.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
if (enImage == null)
|
||||
{
|
||||
return images.movieposter.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
}
|
||||
return enImage;
|
||||
}
|
||||
|
||||
if (images.moviethumb?.Any() ?? false)
|
||||
{
|
||||
return images.moviethumb.OrderBy(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
[HttpGet("poster/tv/{tvdbid}")]
|
||||
public async Task<string> GetTvPoster(int tvdbid)
|
||||
{
|
||||
var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1));
|
||||
|
||||
var images = await FanartTvApi.GetTvImages(tvdbid, key.Value);
|
||||
|
||||
if (images == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (images.tvposter?.Any() ?? false)
|
||||
{
|
||||
var enImage = images.tvposter.Where(x => x.lang == "en").OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
if (enImage == null)
|
||||
{
|
||||
return images.tvposter.OrderByDescending(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
}
|
||||
return enImage;
|
||||
}
|
||||
|
||||
if (images.tvthumb?.Any() ?? false)
|
||||
{
|
||||
return images.tvthumb.OrderBy(x => x.likes).Select(x => x.url).FirstOrDefault();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
@ -63,11 +132,11 @@ namespace Ombi.Controllers
|
|||
if (moviesArray.Any())
|
||||
{
|
||||
var item = rand.Next(moviesArray.Length);
|
||||
var result = await Api.GetMovieImages(moviesArray[item], key.Value);
|
||||
var result = await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value);
|
||||
|
||||
while (!result.moviebackground.Any())
|
||||
{
|
||||
result = await Api.GetMovieImages(moviesArray[item], key.Value);
|
||||
result = await FanartTvApi.GetMovieImages(moviesArray[item].ToString(), key.Value);
|
||||
}
|
||||
|
||||
movieUrl = result.moviebackground[0].url;
|
||||
|
@ -75,11 +144,11 @@ namespace Ombi.Controllers
|
|||
if(tvArray.Any())
|
||||
{
|
||||
var item = rand.Next(tvArray.Length);
|
||||
var result = await Api.GetTvImages(tvArray[item], key.Value);
|
||||
var result = await FanartTvApi.GetTvImages(tvArray[item], key.Value);
|
||||
|
||||
while (!result.showbackground.Any())
|
||||
{
|
||||
result = await Api.GetTvImages(tvArray[item], key.Value);
|
||||
result = await FanartTvApi.GetTvImages(tvArray[item], key.Value);
|
||||
}
|
||||
|
||||
tvUrl = result.showbackground[0].url;
|
||||
|
|
54
src/Ombi/Controllers/RecentlyAddedController.cs
Normal file
54
src/Ombi/Controllers/RecentlyAddedController.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Core.Engine;
|
||||
using Ombi.Core.Models;
|
||||
using Ombi.Models;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
[ApiV1]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class RecentlyAddedController : Controller
|
||||
{
|
||||
public RecentlyAddedController(IRecentlyAddedEngine engine)
|
||||
{
|
||||
_recentlyAdded = engine;
|
||||
}
|
||||
|
||||
private readonly IRecentlyAddedEngine _recentlyAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the recently added movies for the past 7 days
|
||||
/// </summary>
|
||||
[HttpGet("movies")]
|
||||
[ProducesResponseType(typeof(IEnumerable<RecentlyAddedMovieModel>), 200)]
|
||||
public IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies()
|
||||
{
|
||||
return _recentlyAdded.GetRecentlyAddedMovies(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the recently added tv shows for the past 7 days
|
||||
/// </summary>
|
||||
[HttpGet("tv")]
|
||||
[ProducesResponseType(typeof(IEnumerable<RecentlyAddedMovieModel>), 200)]
|
||||
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedShows()
|
||||
{
|
||||
return _recentlyAdded.GetRecentlyAddedTv(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the recently added tv shows for the past 7 days and groups them by season
|
||||
/// </summary>
|
||||
[HttpGet("tv/grouped")]
|
||||
[ProducesResponseType(typeof(IEnumerable<RecentlyAddedMovieModel>), 200)]
|
||||
public IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedShowsGrouped()
|
||||
{
|
||||
return _recentlyAdded.GetRecentlyAddedTv(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow, true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ using Ombi.Settings.Settings.Models.Notifications;
|
|||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Ombi.Api.Github;
|
||||
using Ombi.Core.Engine;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
|
@ -60,7 +61,8 @@ namespace Ombi.Controllers
|
|||
IEmbyApi embyApi,
|
||||
IRadarrSync radarrSync,
|
||||
ICacheService memCache,
|
||||
IGithubApi githubApi)
|
||||
IGithubApi githubApi,
|
||||
IRecentlyAddedEngine engine)
|
||||
{
|
||||
SettingsResolver = resolver;
|
||||
Mapper = mapper;
|
||||
|
@ -69,6 +71,7 @@ namespace Ombi.Controllers
|
|||
_radarrSync = radarrSync;
|
||||
_cache = memCache;
|
||||
_githubApi = githubApi;
|
||||
_recentlyAdded = engine;
|
||||
}
|
||||
|
||||
private ISettingsResolver SettingsResolver { get; }
|
||||
|
@ -78,6 +81,7 @@ namespace Ombi.Controllers
|
|||
private readonly IRadarrSync _radarrSync;
|
||||
private readonly ICacheService _cache;
|
||||
private readonly IGithubApi _githubApi;
|
||||
private readonly IRecentlyAddedEngine _recentlyAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Ombi settings.
|
||||
|
@ -607,7 +611,7 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<EmailNotificationsViewModel>(emailSettings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Email);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Email);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -654,7 +658,7 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<DiscordNotificationsViewModel>(emailSettings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Discord);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Discord);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -689,7 +693,7 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<TelegramNotificationsViewModel>(emailSettings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Telegram);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Telegram);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -723,7 +727,7 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<PushbulletNotificationViewModel>(settings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Pushbullet);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Pushbullet);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -757,7 +761,7 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<PushoverNotificationViewModel>(settings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Pushover);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Pushover);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -792,7 +796,7 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<SlackNotificationsViewModel>(settings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Slack);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Slack);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -826,7 +830,7 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<MattermostNotificationsViewModel>(settings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Mattermost);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Mattermost);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -860,17 +864,62 @@ namespace Ombi.Controllers
|
|||
var model = Mapper.Map<MobileNotificationsViewModel>(settings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
model.NotificationTemplates = await BuildTemplates(NotificationAgent.Mobile);
|
||||
model.NotificationTemplates = BuildTemplates(NotificationAgent.Mobile);
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
private async Task<List<NotificationTemplates>> BuildTemplates(NotificationAgent agent)
|
||||
/// <summary>
|
||||
/// Saves the Newsletter notification settings.
|
||||
/// </summary>
|
||||
/// <param name="model">The model.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("notifications/newsletter")]
|
||||
public async Task<bool> NewsletterSettings([FromBody] NewsletterNotificationViewModel model)
|
||||
{
|
||||
var templates = await TemplateRepository.GetAllTemplates(agent);
|
||||
return templates.OrderBy(x => x.NotificationType.ToString()).ToList();
|
||||
// Save the email settings
|
||||
var settings = Mapper.Map<NewsletterSettings>(model);
|
||||
var result = await Save(settings);
|
||||
|
||||
// Save the templates
|
||||
await TemplateRepository.Update(model.NotificationTemplate);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[HttpPost("notifications/newsletterdatabase")]
|
||||
public async Task<bool> UpdateNewsletterDatabase()
|
||||
{
|
||||
return await _recentlyAdded.UpdateRecentlyAddedDatabase();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Newsletter Notification Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("notifications/newsletter")]
|
||||
public async Task<NewsletterNotificationViewModel> NewsletterSettings()
|
||||
{
|
||||
var settings = await Get<NewsletterSettings>();
|
||||
var model = Mapper.Map<NewsletterNotificationViewModel>(settings);
|
||||
|
||||
// Lookup to see if we have any templates saved
|
||||
var templates = BuildTemplates(NotificationAgent.Email, true);
|
||||
model.NotificationTemplate = templates.FirstOrDefault(x => x.NotificationType == NotificationType.Newsletter);
|
||||
return model;
|
||||
}
|
||||
|
||||
private List<NotificationTemplates> BuildTemplates(NotificationAgent agent, bool showNewsletter = false)
|
||||
{
|
||||
var templates = TemplateRepository.GetAllTemplates(agent);
|
||||
if (!showNewsletter)
|
||||
{
|
||||
// Make sure we do not display the newsletter
|
||||
templates = templates.Where(x => x.NotificationType != NotificationType.Newsletter);
|
||||
}
|
||||
return templates.OrderBy(x => x.NotificationType.ToString()).ToList();
|
||||
}
|
||||
|
||||
private async Task<T> Get<T>()
|
||||
{
|
||||
|
|
|
@ -28,15 +28,19 @@
|
|||
|
||||
<ItemGroup>
|
||||
<!-- Files not to show in IDE -->
|
||||
<Compile Remove="Logs\**" />
|
||||
<Compile Remove="Styles\**" />
|
||||
<Compile Remove="wwwroot\dist\**" />
|
||||
|
||||
<!-- Files not to publish (note that the 'dist' subfolders are re-added below) -->
|
||||
<Content Remove="ClientApp\**" />
|
||||
<Content Remove="Logs\**" />
|
||||
<Content Remove="Styles\**" />
|
||||
<Content Remove="wwwroot\dist\**" />
|
||||
<EmbeddedResource Remove="Logs\**" />
|
||||
<EmbeddedResource Remove="Styles\**" />
|
||||
<EmbeddedResource Remove="wwwroot\dist\**" />
|
||||
<None Remove="Logs\**" />
|
||||
<None Remove="Styles\**" />
|
||||
<None Remove="wwwroot\dist\**" />
|
||||
</ItemGroup>
|
||||
|
|
1740
src/Ombi/package-lock.json
generated
1740
src/Ombi/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -10,37 +10,38 @@
|
|||
"restore": "dotnet restore && npm install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^5.1.2",
|
||||
"@angular/cdk": "^2.0.0-beta.12",
|
||||
"@angular/common": "^5.1.2",
|
||||
"@angular/compiler": "^5.1.2",
|
||||
"@angular/core": "^5.1.2",
|
||||
"@angular/forms": "^5.1.2",
|
||||
"@angular/http": "^5.1.2",
|
||||
"@angular/material": "^2.0.0-beta.12",
|
||||
"@angular/platform-browser": "^5.1.2",
|
||||
"@angular/platform-browser-dynamic": "^5.1.2",
|
||||
"@angular/platform-server": "5.0.0",
|
||||
"@angular/router": "^5.1.2",
|
||||
"@angular/animations": "^5.2.5",
|
||||
"@angular/cdk": "^5.2.1",
|
||||
"@angular/common": "^5.2.5",
|
||||
"@angular/compiler": "^5.2.5",
|
||||
"@angular/core": "^5.2.5",
|
||||
"@angular/forms": "^5.2.5",
|
||||
"@angular/http": "^5.2.5",
|
||||
"@angular/material": "^5.2.1",
|
||||
"@angular/platform-browser": "^5.2.5",
|
||||
"@angular/platform-browser-dynamic": "^5.2.5",
|
||||
"@angular/platform-server": "^5.2.5",
|
||||
"@angular/router": "^5.2.5",
|
||||
"@auth0/angular-jwt": "1.0.0-beta.9",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0-beta.8",
|
||||
"@ng-bootstrap/ng-bootstrap": "^1.0.0",
|
||||
"@ngu/carousel": "^1.4.8",
|
||||
"@ngx-translate/core": "^8.0.0",
|
||||
"@ngx-translate/http-loader": "^2.0.1",
|
||||
"@types/core-js": "^0.9.43",
|
||||
"@types/extract-text-webpack-plugin": "^3.0.0",
|
||||
"@types/core-js": "^0.9.46",
|
||||
"@types/extract-text-webpack-plugin": "^3.0.1",
|
||||
"@types/intro.js": "^2.4.3",
|
||||
"@types/node": "^8.5.2",
|
||||
"@types/webpack": "^3.8.1",
|
||||
"angular-router-loader": "^0.8.1",
|
||||
"angular2-moment": "^1.7.1",
|
||||
"@types/node": "^8.9.4",
|
||||
"@types/webpack": "^3.8.7",
|
||||
"angular-router-loader": "^0.8.2",
|
||||
"angular2-moment": "^1.8.0",
|
||||
"angular2-template-loader": "^0.6.2",
|
||||
"aspnet-webpack": "^2.0.1",
|
||||
"aspnet-webpack": "^2.0.3",
|
||||
"awesome-typescript-loader": "^3.4.1",
|
||||
"bootstrap": "3.3.7",
|
||||
"bootswatch": "3.3.7",
|
||||
"core-js": "^2.5.3",
|
||||
"css": "^2.2.1",
|
||||
"css-loader": "^0.28.7",
|
||||
"css-loader": "^0.28.9",
|
||||
"del": "^3.0.0",
|
||||
"event-source-polyfill": "^0.0.11",
|
||||
"expose-loader": "^0.7.4",
|
||||
|
@ -51,7 +52,7 @@
|
|||
"gulp-run": "^1.7.1",
|
||||
"hammerjs": "^2.0.8",
|
||||
"html-loader": "0.5.1",
|
||||
"jquery": "^3.2.1",
|
||||
"jquery": "^3.3.1",
|
||||
"ng2-cookies": "^1.0.12",
|
||||
"ngx-clipboard": "8.1.1",
|
||||
"ngx-infinite-scroll": "^0.6.1",
|
||||
|
@ -61,22 +62,22 @@
|
|||
"pace-progress": "^1.0.2",
|
||||
"primeng": "5.0.2",
|
||||
"reflect-metadata": "0.1.10",
|
||||
"run-sequence": "^2.2.0",
|
||||
"run-sequence": "^2.2.1",
|
||||
"rxjs": "5.5.2",
|
||||
"sass-loader": "^6.0.6",
|
||||
"style-loader": "^0.19.1",
|
||||
"to-string-loader": "^1.1.5",
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "^5.8.0",
|
||||
"tslint-language-service": "^0.9.7",
|
||||
"typescript": "^2.6.2",
|
||||
"uglify-es": "^3.3.3",
|
||||
"uglifyjs-webpack-plugin": "^1.1.5",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-language-service": "^0.9.8",
|
||||
"typescript": "^2.7.1",
|
||||
"uglify-es": "^3.3.10",
|
||||
"uglifyjs-webpack-plugin": "^1.1.8",
|
||||
"url-loader": "^0.6.2",
|
||||
"webpack": "^3.10.0",
|
||||
"webpack-bundle-analyzer": "^2.9.1",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-bundle-analyzer": "^2.10.0",
|
||||
"webpack-hot-middleware": "^2.21.0",
|
||||
"zone.js": "^0.8.19"
|
||||
"zone.js": "^0.8.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "4.0.4",
|
||||
|
|
|
@ -80,7 +80,7 @@ module.exports = (env: any) => {
|
|||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", Hammer: "hammerjs/hammer" }), // Global identifiers
|
||||
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname, './client')), // Workaround for https://github.com/angular/angular/issues/20357
|
||||
new webpack.ContextReplacementPlugin(/\@angular(\\|\/)core(\\|\/)esm5/, path.join(__dirname,"./client")), // Workaround for https://github.com/angular/angular/issues/20357
|
||||
new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/11580
|
||||
new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, "./ClientApp")), // Workaround for https://github.com/angular/angular/issues/14898
|
||||
extractCSS,
|
||||
|
|
|
@ -67,7 +67,8 @@
|
|||
"Dutch": "Dutch",
|
||||
"Norwegian":"Norwegian"
|
||||
},
|
||||
"OpenMobileApp":"Open Mobile App"
|
||||
"OpenMobileApp":"Open Mobile App",
|
||||
"RecentlyAdded":"Recently Added"
|
||||
},
|
||||
"Search": {
|
||||
"Title": "Search",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue