mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 21:03:17 -07:00
Merge pull request #2089 from tidusjar/feature_recentlyAdded
Feature recently added
This commit is contained in:
commit
5ea81568e5
42 changed files with 2289 additions and 49 deletions
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Ombi.Core.Models;
|
||||
|
||||
namespace Ombi.Core.Engine
|
||||
|
@ -10,5 +11,6 @@ namespace Ombi.Core.Engine
|
|||
IEnumerable<RecentlyAddedMovieModel> GetRecentlyAddedMovies(DateTime from, DateTime to);
|
||||
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(DateTime from, DateTime to, bool groupBySeason);
|
||||
IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(bool groupBySeason);
|
||||
Task<bool> UpdateRecentlyAddedDatabase();
|
||||
}
|
||||
}
|
|
@ -1,27 +1,28 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
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)
|
||||
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)
|
||||
{
|
||||
|
@ -55,6 +56,67 @@ namespace Ombi.Core.Engine
|
|||
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
|
||||
});
|
||||
}
|
||||
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 embyContent)
|
||||
{
|
||||
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);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<RecentlyAddedTvModel> GetRecentlyAddedTv(IQueryable<PlexServerContent> plexTv, IQueryable<EmbyContent> embyTv,
|
||||
bool groupBySeason)
|
||||
{
|
||||
|
|
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; }
|
||||
|
||||
}
|
||||
}
|
|
@ -174,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, string username)
|
||||
{
|
||||
ApplicationUrl = (s?.ApplicationUrl.HasValue() ?? false) ? s.ApplicationUrl : string.Empty;
|
||||
ApplicationName = string.IsNullOrEmpty(s?.ApplicationName) ? "Ombi" : s?.ApplicationName;
|
||||
RequestedUser = 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
9
src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs
Normal file
9
src/Ombi.Schedule/Jobs/Ombi/INewsletterJob.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Schedule.Jobs.Ombi
|
||||
{
|
||||
public interface INewsletterJob : IBaseJob
|
||||
{
|
||||
Task Start();
|
||||
}
|
||||
}
|
563
src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs
Normal file
563
src/Ombi.Schedule/Jobs/Ombi/NewsletterJob.cs
Normal file
|
@ -0,0 +1,563 @@
|
|||
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;
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
var newsletterSettings = await _newsletterSettings.GetSettingsAsync();
|
||||
if (!newsletterSettings.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 => !addedPlexMovieLogIds.Contains(x.Id));
|
||||
var embyContentMoviesToSend = embyContent.Where(x => !addedEmbyMoviesLogIds.Contains(x.Id));
|
||||
|
||||
var plexContentTvToSend = plexContent.Where(x => x.Episodes.Any(e => !addedPlexEpisodesLogIds.Contains(e.Id)));
|
||||
var embyContentTvToSend = embyContent.Where(x => x.Episodes.Any(e => !addedEmbyEpisodesLogIds.Contains(e.Id)));
|
||||
|
||||
var plexContentToSend = plexContentMoviesToSend.Union(plexContentTvToSend);
|
||||
var embyContentToSend = embyContentMoviesToSend.Union(embyContentTvToSend);
|
||||
|
||||
|
||||
var body = await BuildHtml(plexContentToSend, embyContentToSend);
|
||||
|
||||
// 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 html = LoadTemplate(body, template, customization, user.Alias);
|
||||
|
||||
emailTasks.Add(_email.Send(new NotificationMessage { Message = html, Subject = template.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());
|
||||
}
|
||||
|
||||
private string LoadTemplate(string body, NotificationTemplates template, CustomizationSettings settings, string username)
|
||||
{
|
||||
var email = new NewsletterTemplate();
|
||||
|
||||
var resolver = new NotificationMessageResolver();
|
||||
var curlys = new NotificationMessageCurlys();
|
||||
|
||||
curlys.SetupNewsletter(settings, username);
|
||||
|
||||
var parsed = resolver.ParseMessage(template, curlys);
|
||||
|
||||
var html = email.LoadTemplate(parsed.Subject, parsed.Message, body, settings.Logo);
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -218,12 +218,6 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
private async Task StartEmby()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
private bool _disposed;
|
||||
|
@ -236,6 +230,7 @@ namespace Ombi.Schedule.Jobs.Ombi
|
|||
{
|
||||
_plexRepo?.Dispose();
|
||||
_embyRepo?.Dispose();
|
||||
_plexSettings?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -15,7 +15,7 @@ namespace Ombi.Store.Context
|
|||
public OmbiContext()
|
||||
{
|
||||
if (_created) return;
|
||||
|
||||
|
||||
_created = true;
|
||||
Database.Migrate();
|
||||
}
|
||||
|
@ -37,6 +37,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; }
|
||||
|
@ -55,7 +56,7 @@ namespace Ombi.Store.Context
|
|||
{
|
||||
i.StoragePath = string.Empty;
|
||||
}
|
||||
optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath,"Ombi.db")}");
|
||||
optionsBuilder.UseSqlite($"Data Source={Path.Combine(i.StoragePath, "Ombi.db")}");
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder builder)
|
||||
|
@ -70,7 +71,7 @@ namespace Ombi.Store.Context
|
|||
.WithMany(b => b.Episodes)
|
||||
.HasPrincipalKey(x => x.EmbyId)
|
||||
.HasForeignKey(p => p.ParentId);
|
||||
|
||||
|
||||
base.OnModelCreating(builder);
|
||||
}
|
||||
|
||||
|
@ -218,6 +219,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
|
||||
}
|
||||
}
|
934
src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.Designer.cs
generated
Normal file
934
src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.Designer.cs
generated
Normal file
|
@ -0,0 +1,934 @@
|
|||
// <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("20180322085345_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>("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
|
||||
}
|
||||
}
|
||||
}
|
33
src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.cs
Normal file
33
src/Ombi.Store/Migrations/20180322085345_RecentlyAddedLog.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
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),
|
||||
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,22 @@ 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>("Type");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RecentlyAddedLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
|
|
@ -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)
|
||||
|
@ -60,5 +60,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,13 +34,14 @@
|
|||
<i class="fa fa-th-list"></i> {{ 'NavigationBar.Requests' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav navbar-nav">
|
||||
<li id="Requests" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/recentlyadded']">
|
||||
<i class="fa fa-check"></i> {{ 'NavigationBar.RecentlyAdded' | 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 +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;
|
||||
|
|
|
@ -107,6 +107,7 @@ export interface ICustomizationSettings extends ISettings {
|
|||
presetThemeContent: string;
|
||||
presetThemeDisplayName: string;
|
||||
presetThemeVersion: string;
|
||||
recentlyAddedPage: boolean;
|
||||
}
|
||||
|
||||
export interface IThemes {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<settings-menu></settings-menu>
|
||||
|
||||
<div *ngIf="form">
|
||||
<fieldset>
|
||||
<legend>Newsletter</legend>
|
||||
<div class="col-md-6">
|
||||
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enable" formControlName="enabled">
|
||||
<label for="enable">Enabled</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enabled" [(ngModel)]="template.enabled" ng-checked="template.enabled">
|
||||
<label for="enabled">Enable</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="showSubject">
|
||||
<label class="control-label">Subject</label>
|
||||
<div>
|
||||
<input type="text" class="form-control form-control-custom" [(ngModel)]="template.subject" value="{{template.subject}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Message</label>
|
||||
<div>
|
||||
<textarea type="text" class="form-control form-control-custom" [(ngModel)]="template.message" value="{{template.message}}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
|
||||
<button [disabled]="form.invalid" type="button" (click)="updateDatabase()" class="btn btn-info-outline" tooltipPosition="top" pTooltip="I reccomend 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 eventhing that Ombi has found in your libraries will go out on the first email!">Update Database</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
|
@ -0,0 +1,71 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||
|
||||
import { INewsletterNotificationSettings, INotificationTemplates, NotificationType } from "../../interfaces";
|
||||
import { TesterService } from "../../services";
|
||||
import { NotificationService } from "../../services";
|
||||
import { SettingsService } from "../../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./newsletter.component.html",
|
||||
})
|
||||
export class NewsletterComponent implements OnInit {
|
||||
|
||||
public NotificationType = NotificationType;
|
||||
public template: INotificationTemplates;
|
||||
public form: FormGroup;
|
||||
|
||||
constructor(private settingsService: SettingsService,
|
||||
private notificationService: NotificationService,
|
||||
private fb: FormBuilder,
|
||||
private testerService: TesterService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.settingsService.getNewsletterSettings().subscribe(x => {
|
||||
this.template = x.notificationTemplate;
|
||||
|
||||
this.form = this.fb.group({
|
||||
enabled: [x.enabled],
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public updateDatabase() {
|
||||
this.settingsService.updateNewsletterDatabase().subscribe();
|
||||
}
|
||||
|
||||
public onSubmit(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = <INewsletterNotificationSettings>form.value;
|
||||
settings.notificationTemplate = this.template;
|
||||
|
||||
this.settingsService.saveNewsletterSettings(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");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public test(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
this.testerService.discordTest(form.value).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully sent a Discord message, please check the discord channel");
|
||||
} else {
|
||||
this.notificationService.error("There was an error when sending the Discord message. Please check your 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";
|
||||
|
@ -43,6 +44,7 @@ import { SettingsMenuComponent } from "./settingsmenu.component";
|
|||
|
||||
import { AutoCompleteModule, CalendarModule, DialogModule, InputSwitchModule, InputTextModule, MenuModule, RadioButtonModule, TooltipModule } from "primeng/primeng";
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] },
|
||||
{ path: "About", component: AboutComponent, canActivate: [AuthGuard] },
|
||||
|
@ -69,6 +71,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 +122,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>
|
||||
|
|
|
@ -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;
|
||||
|
@ -78,6 +80,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 +610,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 +657,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 +692,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 +726,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 +760,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 +795,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 +829,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 +863,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>()
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue