diff --git a/src/Ombi.Core/Models/UI/UserViewModel.cs b/src/Ombi.Core/Models/UI/UserViewModel.cs index cddc32b90..15045bc0a 100644 --- a/src/Ombi.Core/Models/UI/UserViewModel.cs +++ b/src/Ombi.Core/Models/UI/UserViewModel.cs @@ -18,6 +18,7 @@ namespace Ombi.Core.Models.UI public int EpisodeRequestLimit { get; set; } public RequestQuotaCountModel EpisodeRequestQuota { get; set; } public RequestQuotaCountModel MovieRequestQuota { get; set; } + public int MusicRequestLimit { get; set; } } public class ClaimCheckboxes diff --git a/src/Ombi.Notifications/Agents/DiscordNotification.cs b/src/Ombi.Notifications/Agents/DiscordNotification.cs index d788b471c..84e907053 100644 --- a/src/Ombi.Notifications/Agents/DiscordNotification.cs +++ b/src/Ombi.Notifications/Agents/DiscordNotification.cs @@ -20,8 +20,9 @@ namespace Ombi.Notifications.Agents { public DiscordNotification(IDiscordApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, - IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub, IMusicRequestRepository music) - : base(sn, r, m, t, s, log, sub, music) + IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) + : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/EmailNotification.cs b/src/Ombi.Notifications/Agents/EmailNotification.cs index 3ab045e87..cff1f3b80 100644 --- a/src/Ombi.Notifications/Agents/EmailNotification.cs +++ b/src/Ombi.Notifications/Agents/EmailNotification.cs @@ -22,7 +22,8 @@ namespace Ombi.Notifications.Agents public class EmailNotification : BaseNotification, IEmailNotification { public EmailNotification(ISettingsService settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService c, - ILogger log, UserManager um, IRepository sub, IMusicRequestRepository music) : base(settings, r, m, t, c, log, sub, music) + ILogger log, UserManager um, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(settings, r, m, t, c, log, sub, music, userPref) { EmailProvider = prov; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MattermostNotification.cs b/src/Ombi.Notifications/Agents/MattermostNotification.cs index 9e8a34e3b..37e597854 100644 --- a/src/Ombi.Notifications/Agents/MattermostNotification.cs +++ b/src/Ombi.Notifications/Agents/MattermostNotification.cs @@ -21,7 +21,8 @@ namespace Ombi.Notifications.Agents public class MattermostNotification : BaseNotification, IMattermostNotification { public MattermostNotification(IMattermostApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/MobileNotification.cs b/src/Ombi.Notifications/Agents/MobileNotification.cs index c521b99a4..a16785909 100644 --- a/src/Ombi.Notifications/Agents/MobileNotification.cs +++ b/src/Ombi.Notifications/Agents/MobileNotification.cs @@ -22,7 +22,8 @@ namespace Ombi.Notifications.Agents { public MobileNotification(IOneSignalApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s, IRepository notification, - UserManager um, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + UserManager um, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { _api = api; _logger = log; diff --git a/src/Ombi.Notifications/Agents/PushbulletNotification.cs b/src/Ombi.Notifications/Agents/PushbulletNotification.cs index 0e488bf79..6c6b1f789 100644 --- a/src/Ombi.Notifications/Agents/PushbulletNotification.cs +++ b/src/Ombi.Notifications/Agents/PushbulletNotification.cs @@ -17,7 +17,8 @@ namespace Ombi.Notifications.Agents public class PushbulletNotification : BaseNotification, IPushbulletNotification { public PushbulletNotification(IPushbulletApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/PushoverNotification.cs b/src/Ombi.Notifications/Agents/PushoverNotification.cs index b5d743cc2..86f91dbaa 100644 --- a/src/Ombi.Notifications/Agents/PushoverNotification.cs +++ b/src/Ombi.Notifications/Agents/PushoverNotification.cs @@ -18,7 +18,8 @@ namespace Ombi.Notifications.Agents public class PushoverNotification : BaseNotification, IPushoverNotification { public PushoverNotification(IPushoverApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/SlackNotification.cs b/src/Ombi.Notifications/Agents/SlackNotification.cs index 6c04f5ea6..ee81e9729 100644 --- a/src/Ombi.Notifications/Agents/SlackNotification.cs +++ b/src/Ombi.Notifications/Agents/SlackNotification.cs @@ -18,7 +18,8 @@ namespace Ombi.Notifications.Agents public class SlackNotification : BaseNotification, ISlackNotification { public SlackNotification(ISlackApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, - ISettingsService s, IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t, s, log, sub, music) + ISettingsService s, IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t, s, log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/Agents/TelegramNotification.cs b/src/Ombi.Notifications/Agents/TelegramNotification.cs index 7bcda7c7f..cf463bf99 100644 --- a/src/Ombi.Notifications/Agents/TelegramNotification.cs +++ b/src/Ombi.Notifications/Agents/TelegramNotification.cs @@ -19,7 +19,8 @@ namespace Ombi.Notifications.Agents public TelegramNotification(ITelegramApi api, ISettingsService sn, ILogger log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService s - , IRepository sub, IMusicRequestRepository music) : base(sn, r, m, t,s,log, sub, music) + , IRepository sub, IMusicRequestRepository music, + IRepository userPref) : base(sn, r, m, t,s,log, sub, music, userPref) { Api = api; Logger = log; diff --git a/src/Ombi.Notifications/BaseNotification.cs b/src/Ombi.Notifications/BaseNotification.cs index 287f86455..d351c8283 100644 --- a/src/Ombi.Notifications/BaseNotification.cs +++ b/src/Ombi.Notifications/BaseNotification.cs @@ -19,7 +19,8 @@ namespace Ombi.Notifications.Interfaces public abstract class BaseNotification : INotification where T : Settings.Settings.Models.Settings, new() { protected BaseNotification(ISettingsService settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv, - ISettingsService customization, ILogger> log, IRepository sub, IMusicRequestRepository album) + ISettingsService customization, ILogger> log, IRepository sub, IMusicRequestRepository album, + IRepository notificationUserPreferences) { Settings = settings; TemplateRepository = templateRepo; @@ -31,6 +32,7 @@ namespace Ombi.Notifications.Interfaces RequestSubscription = sub; _log = log; AlbumRepository = album; + UserNotificationPreferences = notificationUserPreferences; } protected ISettingsService Settings { get; } @@ -40,6 +42,7 @@ namespace Ombi.Notifications.Interfaces protected IMusicRequestRepository AlbumRepository { get; } protected CustomizationSettings Customization { get; set; } protected IRepository RequestSubscription { get; set; } + protected IRepository UserNotificationPreferences { get; set; } private ISettingsService CustomizationSettings { get; } private readonly ILogger> _log; @@ -167,7 +170,7 @@ namespace Ombi.Notifications.Interfaces { return new NotificationMessageContent { Disabled = true }; } - var parsed = Parse(model, template); + var parsed = Parse(model, template, agent); return parsed; } @@ -178,25 +181,32 @@ namespace Ombi.Notifications.Interfaces return subs.Select(x => x.User); } - private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template) + protected UserNotificationPreferences GetUserPreference(string userId, NotificationAgent agent) + { + return UserNotificationPreferences.GetAll() + .FirstOrDefault(x => x.Enabled && x.Agent == agent && x.UserId == userId); + } + + private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template, NotificationAgent agent) { var resolver = new NotificationMessageResolver(); var curlys = new NotificationMessageCurlys(); + var preference = GetUserPreference(model.UserId, agent); if (model.RequestType == RequestType.Movie) { _log.LogDebug("Notification options: {@model}, Req: {@MovieRequest}, Settings: {@Customization}", model, MovieRequest, Customization); - curlys.Setup(model, MovieRequest, Customization); + curlys.Setup(model, MovieRequest, Customization, preference); } else if (model.RequestType == RequestType.TvShow) { _log.LogDebug("Notification options: {@model}, Req: {@TvRequest}, Settings: {@Customization}", model, TvRequest, Customization); - curlys.Setup(model, TvRequest, Customization); + curlys.Setup(model, TvRequest, Customization, preference); } else if (model.RequestType == RequestType.Album) { _log.LogDebug("Notification options: {@model}, Req: {@AlbumRequest}, Settings: {@Customization}", model, AlbumRequest, Customization); - curlys.Setup(model, AlbumRequest, Customization); + curlys.Setup(model, AlbumRequest, Customization, preference); } var parsed = resolver.ParseMessage(template, curlys); diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs index 710f64619..5bc74f68e 100644 --- a/src/Ombi.Notifications/NotificationMessageCurlys.cs +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -14,9 +14,10 @@ namespace Ombi.Notifications { public class NotificationMessageCurlys { - public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s) + public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s, UserNotificationPreferences pref) { LoadIssues(opts); + UserPreference = pref.Enabled ? pref.Value : string.Empty; string title; if (req == null) { @@ -58,9 +59,10 @@ namespace Ombi.Notifications AdditionalInformation = opts?.AdditionalInformation ?? string.Empty; } - public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s) + public void Setup(NotificationOptions opts, AlbumRequest req, CustomizationSettings s, UserNotificationPreferences pref) { LoadIssues(opts); + UserPreference = pref.Enabled ? pref.Value : string.Empty; string title; if (req == null) { @@ -101,9 +103,10 @@ namespace Ombi.Notifications Alias = username.Alias.HasValue() ? username.Alias : username.UserName; } - public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s) + public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s, UserNotificationPreferences pref) { LoadIssues(opts); + UserPreference = pref.Enabled ? pref.Value : string.Empty; string title; if (req == null) { @@ -221,6 +224,7 @@ namespace Ombi.Notifications public string IssueStatus { get; set; } public string IssueSubject { get; set; } public string NewIssueComment { get; set; } + public string UserPreference { get; set; } // System Defined private string LongDate => DateTime.Now.ToString("D"); diff --git a/src/Ombi.Store/Entities/OmbiUser.cs b/src/Ombi.Store/Entities/OmbiUser.cs index 9513df818..801a50cb1 100644 --- a/src/Ombi.Store/Entities/OmbiUser.cs +++ b/src/Ombi.Store/Entities/OmbiUser.cs @@ -28,6 +28,7 @@ namespace Ombi.Store.Entities public string UserAccessToken { get; set; } public List NotificationUserIds { get; set; } + public List UserNotificationPreferences { get; set; } [NotMapped] public bool IsEmbyConnect => UserType == UserType.EmbyUser && EmbyConnectUserId.HasValue(); diff --git a/src/Ombi.Store/Entities/UserNotificationPreferences.cs b/src/Ombi.Store/Entities/UserNotificationPreferences.cs new file mode 100644 index 000000000..7196d38ca --- /dev/null +++ b/src/Ombi.Store/Entities/UserNotificationPreferences.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; +using Ombi.Helpers; + +namespace Ombi.Store.Entities +{ + [Table(nameof(UserNotificationPreferences))] + public class UserNotificationPreferences : Entity + { + public string UserId { get; set; } + public NotificationAgent Agent { get; set; } + public bool Enabled { get; set; } + public string Value { get; set; } + + [ForeignKey(nameof(UserId))] + [JsonIgnore] + public OmbiUser User { get; set; } + } +} diff --git a/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs new file mode 100644 index 000000000..d61ea31ba --- /dev/null +++ b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.Designer.cs @@ -0,0 +1,1118 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Ombi.Store.Context; + +namespace Ombi.Store.Migrations +{ + [DbContext(typeof(OmbiContext))] + [Migration("20180919073124_UserNotificationPreferences")] + partial class UserNotificationPreferences + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Type"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("ApplicationConfiguration"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Audit", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AuditArea"); + + b.Property("AuditType"); + + b.Property("DateTime"); + + b.Property("Description"); + + b.Property("User"); + + b.HasKey("Id"); + + b.ToTable("Audit"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("CouchPotatoCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId") + .IsRequired(); + + b.Property("ImdbId"); + + b.Property("ProviderId"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("EmbyContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("EmbyId"); + + b.Property("EpisodeNumber"); + + b.Property("ImdbId"); + + b.Property("ParentId"); + + b.Property("ProviderId"); + + b.Property("SeasonNumber"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("EmbyEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Content"); + + b.Property("SettingsName"); + + b.HasKey("Id"); + + b.ToTable("GlobalSettings"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrAlbumCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ArtistId"); + + b.Property("ForeignAlbumId"); + + b.Property("Monitored"); + + b.Property("PercentOfTracks"); + + b.Property("ReleaseDate"); + + b.Property("Title"); + + b.Property("TrackCount"); + + b.HasKey("Id"); + + b.ToTable("LidarrAlbumCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.LidarrArtistCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ArtistId"); + + b.Property("ArtistName"); + + b.Property("ForeignArtistId"); + + b.Property("Monitored"); + + b.HasKey("Id"); + + b.ToTable("LidarrArtistCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("Message"); + + b.Property("NotificationType"); + + b.Property("Subject"); + + b.HasKey("Id"); + + b.ToTable("NotificationTemplates"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("PlayerId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("Alias"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("EmbyConnectUserId"); + + b.Property("EpisodeRequestLimit"); + + b.Property("LastLoggedIn"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("MovieRequestLimit"); + + b.Property("MusicRequestLimit"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("ProviderUserId"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserAccessToken"); + + b.Property("UserName") + .HasMaxLength(256); + + b.Property("UserType"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("GrandparentKey"); + + b.Property("Key"); + + b.Property("ParentKey"); + + b.Property("SeasonNumber"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("GrandparentKey"); + + b.ToTable("PlexEpisode"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ParentKey"); + + b.Property("PlexContentId"); + + b.Property("PlexServerContentId"); + + b.Property("SeasonKey"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("PlexServerContentId"); + + b.ToTable("PlexSeasonsContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("ImdbId"); + + b.Property("Key"); + + b.Property("Quality"); + + b.Property("ReleaseYear"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.Property("Type"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.ToTable("PlexServerContent"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("HasFile"); + + b.Property("TheMovieDbId"); + + b.HasKey("Id"); + + b.ToTable("RadarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RecentlyAddedLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AddedAt"); + + b.Property("AlbumId"); + + b.Property("ContentId"); + + b.Property("ContentType"); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("Type"); + + b.HasKey("Id"); + + b.ToTable("RecentlyAddedLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("ArtistName"); + + b.Property("Available"); + + b.Property("Cover"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("Disk"); + + b.Property("ForeignAlbumId"); + + b.Property("ForeignArtistId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Rating"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("AlbumRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("ParentRequestId"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("SeriesType"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("ParentRequestId"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("ChildRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("IssueCategory"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Comment"); + + b.Property("Date"); + + b.Property("IssuesId"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("IssuesId"); + + b.HasIndex("UserId"); + + b.ToTable("IssueComments"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description"); + + b.Property("IssueCategoryId"); + + b.Property("IssueId"); + + b.Property("ProviderId"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("ResovledDate"); + + b.Property("Status"); + + b.Property("Subject"); + + b.Property("Title"); + + b.Property("UserReportedId"); + + b.HasKey("Id"); + + b.HasIndex("IssueCategoryId"); + + b.HasIndex("IssueId"); + + b.HasIndex("UserReportedId"); + + b.ToTable("Issues"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("Background"); + + b.Property("Denied"); + + b.Property("DeniedReason"); + + b.Property("DigitalReleaseDate"); + + b.Property("ImdbId"); + + b.Property("IssueId"); + + b.Property("MarkedAsApproved"); + + b.Property("MarkedAsAvailable"); + + b.Property("MarkedAsDenied"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RequestType"); + + b.Property("RequestedDate"); + + b.Property("RequestedUserId"); + + b.Property("RootPathOverride"); + + b.Property("Status"); + + b.Property("TheMovieDbId"); + + b.Property("Title"); + + b.HasKey("Id"); + + b.HasIndex("RequestedUserId"); + + b.ToTable("MovieRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeCount"); + + b.Property("RequestDate"); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestLog"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Background"); + + b.Property("ImdbId"); + + b.Property("Overview"); + + b.Property("PosterPath"); + + b.Property("QualityOverride"); + + b.Property("ReleaseDate"); + + b.Property("RootFolder"); + + b.Property("Status"); + + b.Property("Title"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("TvRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RequestId"); + + b.Property("RequestType"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("RequestSubscription"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SickRageEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("EpisodeNumber"); + + b.Property("HasFile"); + + b.Property("SeasonNumber"); + + b.Property("TvDbId"); + + b.HasKey("Id"); + + b.ToTable("SonarrEpisodeCache"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Token"); + + b.Property("UserId"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Tokens"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AirDate"); + + b.Property("Approved"); + + b.Property("Available"); + + b.Property("EpisodeNumber"); + + b.Property("Requested"); + + b.Property("SeasonId"); + + b.Property("Title"); + + b.Property("Url"); + + b.HasKey("Id"); + + b.HasIndex("SeasonId"); + + b.ToTable("EpisodeRequests"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChildRequestId"); + + b.Property("SeasonNumber"); + + b.HasKey("Id"); + + b.HasIndex("ChildRequestId"); + + b.ToTable("SeasonRequests"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b => + { + b.HasOne("Ombi.Store.Entities.EmbyContent", "Series") + .WithMany("Episodes") + .HasForeignKey("ParentId") + .HasPrincipalKey("EmbyId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.NotificationUserId", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("NotificationUserIds") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series") + .WithMany("Episodes") + .HasForeignKey("GrandparentKey") + .HasPrincipalKey("Key") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b => + { + b.HasOne("Ombi.Store.Entities.PlexServerContent") + .WithMany("Seasons") + .HasForeignKey("PlexServerContentId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.AlbumRequest", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest") + .WithMany("ChildRequests") + .HasForeignKey("ParentRequestId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b => + { + b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues") + .WithMany("Comments") + .HasForeignKey("IssuesId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b => + { + b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory") + .WithMany() + .HasForeignKey("IssueCategoryId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.Requests.MovieRequests") + .WithMany("Issues") + .HasForeignKey("IssueId"); + + b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported") + .WithMany() + .HasForeignKey("UserReportedId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser") + .WithMany() + .HasForeignKey("RequestedUserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.RequestSubscription", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.Tokens", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany() + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => + { + b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") + .WithMany("Episodes") + .HasForeignKey("SeasonId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b => + { + b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest") + .WithMany("SeasonRequests") + .HasForeignKey("ChildRequestId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs new file mode 100644 index 000000000..adb062af7 --- /dev/null +++ b/src/Ombi.Store/Migrations/20180919073124_UserNotificationPreferences.cs @@ -0,0 +1,43 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Ombi.Store.Migrations +{ + public partial class UserNotificationPreferences : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "UserNotificationPreferences", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(nullable: true), + Agent = table.Column(nullable: false), + Enabled = table.Column(nullable: false), + Value = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_UserNotificationPreferences", x => x.Id); + table.ForeignKey( + name: "FK_UserNotificationPreferences_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_UserNotificationPreferences_UserId", + table: "UserNotificationPreferences", + column: "UserId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "UserNotificationPreferences"); + } + } +} diff --git a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs index 64400e58c..5e5aa23c6 100644 --- a/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs +++ b/src/Ombi.Store/Migrations/OmbiContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace Ombi.Store.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.1-rtm-30846"); + .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => { @@ -870,6 +870,26 @@ namespace Ombi.Store.Migrations b.ToTable("Tokens"); }); + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Agent"); + + b.Property("Enabled"); + + b.Property("UserId"); + + b.Property("Value"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserNotificationPreferences"); + }); + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => { b.Property("Id") @@ -1068,6 +1088,13 @@ namespace Ombi.Store.Migrations .HasForeignKey("UserId"); }); + modelBuilder.Entity("Ombi.Store.Entities.UserNotificationPreferences", b => + { + b.HasOne("Ombi.Store.Entities.OmbiUser", "User") + .WithMany("UserNotificationPreferences") + .HasForeignKey("UserId"); + }); + modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b => { b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season") diff --git a/src/Ombi/ClientApp/app/interfaces/IUser.ts b/src/Ombi/ClientApp/app/interfaces/IUser.ts index 9e14dcfb4..ff1e7f944 100644 --- a/src/Ombi/ClientApp/app/interfaces/IUser.ts +++ b/src/Ombi/ClientApp/app/interfaces/IUser.ts @@ -13,6 +13,7 @@ export interface IUser { hasLoggedIn: boolean; movieRequestLimit: number; episodeRequestLimit: number; + musicRequestLimit: number; userAccessToken: string; // FOR UI @@ -70,3 +71,22 @@ export interface IMassEmailModel { body: string; users: IUser[]; } + +export interface INotificationPreferences { + id: number; + userId: string; + agent: INotificationAgent; + enabled: boolean; + value: string; +} + +export enum INotificationAgent { + Email = 0, + Discord = 1, + Pushbullet = 2, + Pushover = 3, + Telegram = 4, + Slack = 5, + Mattermost = 6, + Mobile = 7, +} diff --git a/src/Ombi/ClientApp/app/services/identity.service.ts b/src/Ombi/ClientApp/app/services/identity.service.ts index e3dc1d8ad..bce159ebe 100644 --- a/src/Ombi/ClientApp/app/services/identity.service.ts +++ b/src/Ombi/ClientApp/app/services/identity.service.ts @@ -4,7 +4,7 @@ import { Injectable } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { Observable } from "rxjs"; -import { ICheckbox, ICreateWizardUser, IIdentityResult, IResetPasswordToken, IUpdateLocalUser, IUser, IWizardUserResult } from "../interfaces"; +import { ICheckbox, ICreateWizardUser, IIdentityResult, INotificationPreferences, IResetPasswordToken, IUpdateLocalUser, IUser, IWizardUserResult } from "../interfaces"; import { ServiceHelpers } from "./service.helpers"; @Injectable() @@ -43,6 +43,11 @@ export class IdentityService extends ServiceHelpers { public updateUser(user: IUser): Observable { return this.http.put(this.url, JSON.stringify(user), {headers: this.headers}); } + + public updateNotificationPreferences(pref: INotificationPreferences[]): Observable { + return this.http.post(`${this.url}NotificationPreferences`, JSON.stringify(pref), {headers: this.headers}); + } + public updateLocalUser(user: IUpdateLocalUser): Observable { return this.http.put(this.url + "local", JSON.stringify(user), {headers: this.headers}); } @@ -67,6 +72,13 @@ export class IdentityService extends ServiceHelpers { return this.http.post(`${this.url}welcomeEmail`, JSON.stringify(user), {headers: this.headers}); } + public getNotificationPreferences(): Observable { + return this.http.get(`${this.url}notificationpreferences`, {headers: this.headers}); + } + public getNotificationPreferencesForUser(userId: string): Observable { + return this.http.get(`${this.url}notificationpreferences/${userId}`, {headers: this.headers}); + } + public hasRole(role: string): boolean { const roles = localStorage.getItem("roles") as string[] | null; if (roles) { diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.html deleted file mode 100644 index 683bd5620..000000000 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-add.component.html +++ /dev/null @@ -1,79 +0,0 @@ - -

Create User

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

User: {{user.userName}}

- - - - - - -
- - - - -
-
-
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts deleted file mode 100644 index a83ef5a97..000000000 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component } from "@angular/core"; -import { Router } from "@angular/router"; -import { ConfirmationService } from "primeng/primeng"; - -import { ActivatedRoute } from "@angular/router"; -import { IUser } from "../interfaces"; -import { IdentityService } from "../services"; -import { NotificationService } from "../services"; - -@Component({ - templateUrl: "./usermanagement-edit.component.html", -}) -export class UserManagementEditComponent { - public user: IUser; - public userId: string; - - constructor(private identityService: IdentityService, - private route: ActivatedRoute, - private notificationService: NotificationService, - private router: Router, - private confirmationService: ConfirmationService) { - this.route.params - .subscribe((params: any) => { - this.userId = params.id; - - this.identityService.getUserById(this.userId).subscribe(x => { - this.user = x; - }); - }); - } - - public delete() { - - this.confirmationService.confirm({ - message: "Are you sure that you want to delete this user? If this user has any requests they will also be deleted.", - header: "Are you sure?", - icon: "fa fa-trash", - accept: () => { - this.identityService.deleteUser(this.user).subscribe(x => { - if (x.successful) { - this.notificationService.success(`The user ${this.user.userName} was deleted`); - this.router.navigate(["usermanagement"]); - } else { - x.errors.forEach((val) => { - this.notificationService.error(val); - }); - } - - }); - }, - reject: () => { - return; - }, - }); - } - - public resetPassword() { - this.identityService.submitResetPassword(this.user.emailAddress).subscribe(x => { - if (x.successful) { - this.notificationService.success(`Sent reset password email to ${this.user.emailAddress}`); - this.router.navigate(["usermanagement"]); - } else { - x.errors.forEach((val) => { - this.notificationService.error(val); - }); - } - - }); - } - - public update() { - const hasClaims = this.user.claims.some((item) => { - if (item.enabled) { return true; } - - return false; - }); - - if (!hasClaims) { - this.notificationService.error("Please assign a role"); - return; - } - - this.identityService.updateUser(this.user).subscribe(x => { - if (x.successful) { - this.notificationService.success(`The user ${this.user.userName} has been updated successfully`); - this.router.navigate(["usermanagement"]); - } else { - x.errors.forEach((val) => { - this.notificationService.error(val); - }); - } - }); - } - -} diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html new file mode 100644 index 000000000..f7bf702a0 --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.html @@ -0,0 +1,158 @@ +

Create User

+

User: {{user.userName}}

+ +
+ + +
+
+ +
+ + + + +
+
+
+
\ No newline at end of file diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.ts new file mode 100644 index 000000000..fc79e0201 --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-user.component.ts @@ -0,0 +1,164 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; + +import { ICheckbox, INotificationAgent, INotificationPreferences, IUser, UserType } from "../interfaces"; +import { IdentityService, NotificationService } from "../services"; + +import { ConfirmationService } from "primeng/primeng"; + +@Component({ + templateUrl: "./usermanagement-user.component.html", +}) +export class UserManagementUserComponent implements OnInit { + + public user: IUser; + public userId: string; + public availableClaims: ICheckbox[]; + public confirmPass: ""; + public notificationPreferences: INotificationPreferences[]; + + public NotificationAgent = INotificationAgent; + public edit: boolean; + + constructor(private identityService: IdentityService, + private notificationService: NotificationService, + private router: Router, + private route: ActivatedRoute, + private confirmationService: ConfirmationService) { + this.route.params + .subscribe((params: any) => { + if(params.id) { + this.userId = params.id; + this.edit = true; + this.identityService.getUserById(this.userId).subscribe(x => { + this.user = x; + }); + } + }); + + } + + public ngOnInit() { + this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); + if(this.edit) { + this.identityService.getNotificationPreferencesForUser(this.userId).subscribe(x => this.notificationPreferences = x); + } else { + this.identityService.getNotificationPreferences().subscribe(x => this.notificationPreferences = x); + } + if(!this.edit) { + this.user = { + alias: "", + claims: [], + emailAddress: "", + id: "", + password: "", + userName: "", + userType: UserType.LocalUser, + checked: false, + hasLoggedIn: false, + lastLoggedIn: new Date(), + episodeRequestLimit: 0, + movieRequestLimit: 0, + userAccessToken: "", + musicRequestLimit: 0, + }; + } + } + + public create() { + this.user.claims = this.availableClaims; + + if (this.user.password) { + if (this.user.password !== this.confirmPass) { + this.notificationService.error("Passwords do not match"); + return; + } + } + const hasClaims = this.availableClaims.some((item) => { + if (item.enabled) { return true; } + + return false; + }); + + if (!hasClaims) { + this.notificationService.error("Please assign a role"); + return; + } + + this.identityService.createUser(this.user).subscribe(x => { + if (x.successful) { + this.notificationService.success(`The user ${this.user.userName} has been created successfully`); + this.router.navigate(["usermanagement"]); + } else { + x.errors.forEach((val) => { + this.notificationService.error(val); + }); + } + }); + } + + public delete() { + + this.confirmationService.confirm({ + message: "Are you sure that you want to delete this user? If this user has any requests they will also be deleted.", + header: "Are you sure?", + icon: "fa fa-trash", + accept: () => { + this.identityService.deleteUser(this.user).subscribe(x => { + if (x.successful) { + this.notificationService.success(`The user ${this.user.userName} was deleted`); + this.router.navigate(["usermanagement"]); + } else { + x.errors.forEach((val) => { + this.notificationService.error(val); + }); + } + + }); + }, + reject: () => { + return; + }, + }); + } + + public resetPassword() { + this.identityService.submitResetPassword(this.user.emailAddress).subscribe(x => { + if (x.successful) { + this.notificationService.success(`Sent reset password email to ${this.user.emailAddress}`); + this.router.navigate(["usermanagement"]); + } else { + x.errors.forEach((val) => { + this.notificationService.error(val); + }); + } + + }); + } + + public update() { + const hasClaims = this.user.claims.some((item) => { + if (item.enabled) { return true; } + + return false; + }); + + if (!hasClaims) { + this.notificationService.error("Please assign a role"); + return; + } + + this.identityService.updateUser(this.user).subscribe(x => { + if (x.successful) { + this.identityService.updateNotificationPreferences(this.notificationPreferences).subscribe(); + this.notificationService.success(`The user ${this.user.userName} has been updated successfully`); + this.router.navigate(["usermanagement"]); + } else { + x.errors.forEach((val) => { + this.notificationService.error(val); + }); + } + }); + } + +} diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html index 431ea7ddf..856e8785c 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.component.html @@ -2,7 +2,7 @@ - +
@@ -116,7 +116,7 @@ Emby User - Details/Edit + Details/Edit diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts index 1a91cf295..91fa628e4 100644 --- a/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement.module.ts @@ -7,9 +7,8 @@ import { ConfirmationService, ConfirmDialogModule, MultiSelectModule, SidebarMod import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { UpdateDetailsComponent } from "./updatedetails.component"; -import { UserManagementAddComponent } from "./usermanagement-add.component"; -import { UserManagementEditComponent } from "./usermanagement-edit.component"; import { UserManagementComponent } from "./usermanagement.component"; +import { UserManagementUserComponent } from "./usermanagement-user.component"; import { PipeModule } from "../pipes/pipe.module"; import { IdentityService, PlexService } from "../services"; @@ -23,8 +22,8 @@ import { SharedModule } from "../shared/shared.module"; const routes: Routes = [ { path: "", component: UserManagementComponent, canActivate: [AuthGuard] }, - { path: "add", component: UserManagementAddComponent, canActivate: [AuthGuard] }, - { path: "edit/:id", component: UserManagementEditComponent, canActivate: [AuthGuard] }, + { path: "user", component: UserManagementUserComponent, canActivate: [AuthGuard] }, + { path: "user/:id", component: UserManagementUserComponent, canActivate: [AuthGuard] }, { path: "updatedetails", component: UpdateDetailsComponent, canActivate: [AuthGuard] }, ]; @@ -45,10 +44,9 @@ const routes: Routes = [ ], declarations: [ UserManagementComponent, - UserManagementAddComponent, - UserManagementEditComponent, UpdateDetailsComponent, AddPlexUserComponent, + UserManagementUserComponent, ], entryComponents:[ AddPlexUserComponent, diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index 3ecb9c3e7..e0b864185 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -62,6 +62,8 @@ namespace Ombi.Controllers IRepository notificationRepository, IRepository subscriptionRepository, ISettingsService umSettings, + IRepository notificationPreferences, + IMusicRequestRepository musicRepo), IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine) { @@ -73,6 +75,7 @@ namespace Ombi.Controllers CustomizationSettings = c; WelcomeEmail = welcome; MovieRepo = m; + MusicRepo = musicRepo; TvRepo = t; _log = l; _plexApi = plexApi; @@ -86,6 +89,7 @@ namespace Ombi.Controllers _userManagementSettings = umSettings; TvRequestEngine = tvRequestEngine; MovieRequestEngine = movieRequestEngine; + _userNotificationPreferences = notificationPreferences; } private OmbiUserManager UserManager { get; } @@ -101,6 +105,7 @@ namespace Ombi.Controllers private ITvRequestRepository TvRepo { get; } private IMovieRequestEngine MovieRequestEngine { get; } private ITvRequestEngine TvRequestEngine { get; } + private IMusicRequestRepository MusicRepo { get; } private readonly ILogger _log; private readonly IPlexApi _plexApi; private readonly ISettingsService _plexSettings; @@ -109,6 +114,7 @@ namespace Ombi.Controllers private readonly IRepository _requestLogRepository; private readonly IRepository _notificationRepository; private readonly IRepository _requestSubscriptionRepository; + private readonly IRepository _userNotificationPreferences; /// /// This is what the Wizard will call when creating the user for the very first time. @@ -128,7 +134,7 @@ namespace Ombi.Controllers if (users.Any(x => !x.UserName.Equals("api", StringComparison.InvariantCultureIgnoreCase))) { // No one should be calling this. Only the wizard - return new SaveWizardResult{ Result = false, Errors = new List {"Looks like there is an existing user!"} }; + return new SaveWizardResult { Result = false, Errors = new List { "Looks like there is an existing user!" } }; } if (user.UsePlexAdminAccount) @@ -298,7 +304,8 @@ namespace Ombi.Controllers LastLoggedIn = user.LastLoggedIn, HasLoggedIn = user.LastLoggedIn.HasValue, EpisodeRequestLimit = user.EpisodeRequestLimit ?? 0, - MovieRequestLimit = user.MovieRequestLimit ?? 0 + MovieRequestLimit = user.MovieRequestLimit ?? 0, + MusicRequestLimit = user.MusicRequestLimit ?? 0, }; foreach (var role in userRoles) @@ -360,6 +367,7 @@ namespace Ombi.Controllers UserType = UserType.LocalUser, MovieRequestLimit = user.MovieRequestLimit, EpisodeRequestLimit = user.EpisodeRequestLimit, + MusicRequestLimit = user.MusicRequestLimit, UserAccessToken = Guid.NewGuid().ToString("N"), }; var userResult = await UserManager.CreateAsync(ombiUser, user.Password); @@ -503,6 +511,7 @@ namespace Ombi.Controllers user.Email = ui.EmailAddress; user.MovieRequestLimit = ui.MovieRequestLimit; user.EpisodeRequestLimit = ui.EpisodeRequestLimit; + user.MusicRequestLimit = ui.MusicRequestLimit; var updateResult = await UserManager.UpdateAsync(user); if (!updateResult.Succeeded) { @@ -575,7 +584,8 @@ namespace Ombi.Controllers // We need to delete all the requests first var moviesUserRequested = MovieRepo.GetAll().Where(x => x.RequestedUserId == userId); var tvUserRequested = TvRepo.GetChild().Where(x => x.RequestedUserId == userId); - + var musicRequested = MusicRepo.GetAll().Where(x => x.RequestedUserId == userId); + if (moviesUserRequested.Any()) { await MovieRepo.DeleteRange(moviesUserRequested); @@ -585,6 +595,11 @@ namespace Ombi.Controllers await TvRepo.DeleteChildRange(tvUserRequested); } + if (musicRequested.Any()) + { + await MusicRepo.DeleteRange(musicRequested); + } + // Delete any issues and request logs var issues = _issuesRepository.GetAll().Where(x => x.UserReportedId == userId); var issueComments = _issueCommentsRepository.GetAll().Where(x => x.UserId == userId); @@ -601,9 +616,9 @@ namespace Ombi.Controllers { await _issueCommentsRepository.DeleteRange(issueComments); } - + // Delete the Subscriptions and mobile notification ids - var subs = _requestSubscriptionRepository.GetAll().Where(x => x.UserId == userId); + var subs = _requestSubscriptionRepository.GetAll().Where(x => x.UserId == userId); var mobileIds = _notificationRepository.GetAll().Where(x => x.UserId == userId); if (subs.Any()) { @@ -803,6 +818,92 @@ namespace Ombi.Controllers return user.UserAccessToken; } + [HttpGet("notificationpreferences")] + public async Task> GetUserPreferences() + { + var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); + return await GetPreferences(user); + } + + [HttpGet("notificationpreferences/{userId}")] + public async Task> GetUserPreferences(string userId) + { + var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId); + return await GetPreferences(user); + } + + private readonly List _excludedAgents = new List + { + NotificationAgent.Email, + NotificationAgent.Mobile + }; + private async Task> GetPreferences(OmbiUser user) + { + var userPreferences = await _userNotificationPreferences.GetAll().Where(x => x.UserId == user.Id).ToListAsync(); + + var agents = Enum.GetValues(typeof(NotificationAgent)).Cast().Where(x => !_excludedAgents.Contains(x)); + foreach (var a in agents) + { + var agent = userPreferences.FirstOrDefault(x => x.Agent == a); + if (agent == null) + { + // Create the default + userPreferences.Add(new UserNotificationPreferences + { + Agent = a, + UserId = user.Id, + }); + } + } + + return userPreferences; + } + + [HttpPost("NotificationPreferences")] + public async Task AddUserNotificationPreference([FromBody] List preferences) + { + foreach (var pref in preferences) + { + + // Make sure the user exists + var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == pref.UserId); + if (user == null) + { + return NotFound(); + } + // Check if we are editing a different user than ourself, if we are then we need to power user role + var me = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name); + if (!me.Id.Equals(user.Id, StringComparison.InvariantCultureIgnoreCase)) + { + var isPowerUser = await UserManager.IsInRoleAsync(me, OmbiRoles.PowerUser); + var isAdmin = await UserManager.IsInRoleAsync(me, OmbiRoles.Admin); + if (!isPowerUser && !isAdmin) + { + return Unauthorized(); + } + } + + // Make sure we don't already have a preference for this agent + var existingPreference = await _userNotificationPreferences.GetAll() + .FirstOrDefaultAsync(x => x.UserId == user.Id && x.Agent == pref.Agent); + if (existingPreference != null) + { + // Update it + existingPreference.Value = pref.Value; + existingPreference.Enabled = pref.Enabled; + } + await _userNotificationPreferences.Add(new UserNotificationPreferences + { + Agent = pref.Agent, + Enabled = pref.Enabled, + UserId = pref.UserId, + Value = pref.Value + }); + + } + return Json(true); + } + private async Task> AddRoles(IEnumerable roles, OmbiUser ombiUser) { var roleResult = new List(); diff --git a/src/Ombi/Models/Identity/AddNotificationPreference.cs b/src/Ombi/Models/Identity/AddNotificationPreference.cs new file mode 100644 index 000000000..51dc7f6fe --- /dev/null +++ b/src/Ombi/Models/Identity/AddNotificationPreference.cs @@ -0,0 +1,12 @@ +using Ombi.Helpers; + +namespace Ombi.Models.Identity +{ + public class AddNotificationPreference + { + public NotificationAgent Agent { get; set; } + public string UserId { get; set; } + public string Value { get; set; } + public bool Enabled { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi/Ombi.csproj b/src/Ombi/Ombi.csproj index f01e7c05d..7489ad77f 100644 --- a/src/Ombi/Ombi.csproj +++ b/src/Ombi/Ombi.csproj @@ -8,7 +8,7 @@ $(SemVer) $(FullVer) - 2.8 + 3.0 false