From ffab4b79814fd26dd1501cc4484d7380138353bd Mon Sep 17 00:00:00 2001 From: "Jamie.Rees" Date: Fri, 7 Jul 2017 13:25:41 +0100 Subject: [PATCH] Lots of refactoring #865 Starting to refactor the Angular Modules now --- src/Ombi.Core/Engine/Interfaces/BaseEngine.cs | 14 +- src/Ombi.Core/Engine/MovieRequestEngine.cs | 3 +- src/Ombi.Core/Engine/TvRequestEngine.cs | 153 ++------------ .../{ => Helpers}/NotificationHelper.cs | 0 src/Ombi.Core/Helpers/TvShowRequestBuilder.cs | 197 ++++++++++++++++++ src/Ombi.Core/{ => Senders}/IMovieSender.cs | 0 .../{ => Senders}/INotificationHelper.cs | 0 src/Ombi.Core/{ => Senders}/ITvSender.cs | 0 src/Ombi.Core/{ => Senders}/MovieSender.cs | 0 .../{ => Senders}/MovieSenderResult.cs | 0 src/Ombi.Core/{ => Senders}/TvSender.cs | 0 .../NotificationMessageResolverTests.cs | 32 +++ .../Ombi.Notifications.Tests.csproj | 19 ++ .../NotificationMessageContent.cs | 9 + .../NotificationMessageCurlys.cs | 73 +++++++ .../NotificationMessageResolver.cs | 165 ++++++++------- src/Ombi.sln | 45 ++-- src/Ombi/ClientApp/app/app.module.ts | 18 +- .../ClientApp/app/login/login.component.html | 4 +- .../ClientApp/app/search/search.module.ts | 45 ++++ .../app/services/identity.service.ts | 4 + .../usermanagement-edit.component.html | 53 +++++ .../usermanagement-edit.component.ts | 31 +++ src/Ombi/Controllers/IdentityController.cs | 4 +- src/Ombi/wwwroot/images/ms-icon-150x150.png | Bin 0 -> 12016 bytes src/Ombi/wwwroot/images/ms-icon-310x310.png | Bin 0 -> 36529 bytes 26 files changed, 602 insertions(+), 267 deletions(-) rename src/Ombi.Core/{ => Helpers}/NotificationHelper.cs (100%) create mode 100644 src/Ombi.Core/Helpers/TvShowRequestBuilder.cs rename src/Ombi.Core/{ => Senders}/IMovieSender.cs (100%) rename src/Ombi.Core/{ => Senders}/INotificationHelper.cs (100%) rename src/Ombi.Core/{ => Senders}/ITvSender.cs (100%) rename src/Ombi.Core/{ => Senders}/MovieSender.cs (100%) rename src/Ombi.Core/{ => Senders}/MovieSenderResult.cs (100%) rename src/Ombi.Core/{ => Senders}/TvSender.cs (100%) create mode 100644 src/Ombi.Notifications.Tests/NotificationMessageResolverTests.cs create mode 100644 src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj create mode 100644 src/Ombi.Notifications/NotificationMessageContent.cs create mode 100644 src/Ombi.Notifications/NotificationMessageCurlys.cs create mode 100644 src/Ombi/ClientApp/app/search/search.module.ts create mode 100644 src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html create mode 100644 src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts create mode 100644 src/Ombi/wwwroot/images/ms-icon-150x150.png create mode 100644 src/Ombi/wwwroot/images/ms-icon-310x310.png diff --git a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs index 7b4274b3f..db5a21b50 100644 --- a/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs +++ b/src/Ombi.Core/Engine/Interfaces/BaseEngine.cs @@ -1,5 +1,4 @@ -using Ombi.Core.Claims; -using Ombi.Core.Rule; +using Ombi.Core.Rule; using System.Collections.Generic; using System.Security.Principal; using System.Threading.Tasks; @@ -18,24 +17,13 @@ namespace Ombi.Core.Engine.Interfaces } protected IPrincipal User { get; } - protected IRuleEvaluator Rules { get; } - protected string Username => User.Identity.Name; protected bool HasRole(string roleName) { return User.IsInRole(roleName); } - - protected bool ShouldSendNotification(BaseRequest req) - { - var sendNotification = !req.Approved; /*|| !prSettings.IgnoreNotifyForAutoApprovedRequests;*/ - - if (HasRole(OmbiClaims.Admin)) - sendNotification = false; // Don't bother sending a notification if the user is an admin - return sendNotification; - } public async Task> RunRequestRules(BaseRequest model) { diff --git a/src/Ombi.Core/Engine/MovieRequestEngine.cs b/src/Ombi.Core/Engine/MovieRequestEngine.cs index 6f21f79df..4ea334571 100644 --- a/src/Ombi.Core/Engine/MovieRequestEngine.cs +++ b/src/Ombi.Core/Engine/MovieRequestEngine.cs @@ -182,7 +182,8 @@ namespace Ombi.Core.Engine { await MovieRepository.Add(model); - if (ShouldSendNotification(model)) + var result = await RunSpecificRule(model, SpecificRules.CanSendNotification); + if (result.Success) { NotificationHelper.NewRequest(model); } diff --git a/src/Ombi.Core/Engine/TvRequestEngine.cs b/src/Ombi.Core/Engine/TvRequestEngine.cs index b7261cb63..b6a71aac4 100644 --- a/src/Ombi.Core/Engine/TvRequestEngine.cs +++ b/src/Ombi.Core/Engine/TvRequestEngine.cs @@ -12,6 +12,7 @@ using System.Security.Principal; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Ombi.Core.Engine.Interfaces; +using Ombi.Core.Helpers; using Ombi.Core.IdentityResolver; using Ombi.Core.Rule; using Ombi.Core.Rule.Interfaces; @@ -42,128 +43,17 @@ namespace Ombi.Core.Engine public async Task RequestTvShow(SearchTvShowViewModel tv) { - var showInfo = await TvApi.ShowLookupByTheTvDbId(tv.Id); - DateTime.TryParse(showInfo.premiered, out DateTime firstAir); - - // For some reason the poster path is always http - var posterPath = showInfo.image?.medium.Replace("http:", "https:"); - - var tvRequests = new List(); - // Only have the TV requests we actually requested and not everything - foreach (var season in tv.SeasonRequests) - { - for (int i = season.Episodes.Count - 1; i >= 0; i--) - { - if (!season.Episodes[i].Requested) - { - season.Episodes.RemoveAt(i); // Remove the episode since it's not requested - } - } - - if (season.Episodes.Any()) - { - tvRequests.Add(season); - } - } - var user = await UserManager.GetUser(User.Identity.Name); - var childRequest = new ChildRequests - { - Id = tv.Id, - RequestType = RequestType.TvShow, - RequestedDate = DateTime.UtcNow, - Approved = false, - RequestedUserId = user.Id, - SeasonRequests = new List() - }; - if (tv.RequestAll) - { - var episodes = await TvApi.EpisodeLookup(showInfo.id); - foreach (var ep in episodes) - { - var season = childRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season); - if (season == null) - { - childRequest.SeasonRequests.Add(new SeasonRequests - { - Episodes = new List{ - new EpisodeRequests - { - EpisodeNumber = ep.number, - AirDate = DateTime.Parse(ep.airdate), - Title = ep.name, - Url = ep.url - } - }, - SeasonNumber = ep.season, - }); - } - else - { - season.Episodes.Add(new EpisodeRequests - { - EpisodeNumber = ep.number, - AirDate = DateTime.Parse(ep.airdate), - Title = ep.name, - Url = ep.url - }); - } - } + var tvBuilder = new TvShowRequestBuilder(TvApi); + (await tvBuilder + .GetShowInfo(tv.Id)) + .CreateTvList(tv) + .CreateChild(tv, user.Id); - } - else if (tv.LatestSeason) - { - var episodes = await TvApi.EpisodeLookup(showInfo.id); - var latest = episodes.OrderBy(x => x.season).FirstOrDefault(); - var episodesRequests = new List(); - foreach (var ep in episodes) - { - episodesRequests.Add(new EpisodeRequests - { - EpisodeNumber = ep.number, - AirDate = DateTime.Parse(ep.airdate), - Title = ep.name, - Url = ep.url - }); - } - childRequest.SeasonRequests.Add(new SeasonRequests - { - Episodes = episodesRequests, - SeasonNumber = latest.season, - }); - } - else if (tv.FirstSeason) - { - var episodes = await TvApi.EpisodeLookup(showInfo.id); - var first = episodes.OrderByDescending(x => x.season).FirstOrDefault(); - var episodesRequests = new List(); - foreach (var ep in episodes) - { - if (ep.season == first.season) - { - episodesRequests.Add(new EpisodeRequests - { - EpisodeNumber = ep.number, - AirDate = DateTime.Parse(ep.airdate), - Title = ep.name, - Url = ep.url - }); - } - } - childRequest.SeasonRequests.Add(new SeasonRequests - { - Episodes = episodesRequests, - SeasonNumber = first.season, - }); - } - else - { - // It's a custom request - childRequest.SeasonRequests = tvRequests; - } - - var ruleResults = await RunRequestRules(childRequest); + await tvBuilder.BuildEpisodes(tv); + + var ruleResults = await RunRequestRules(tvBuilder.ChildRequest); var results = ruleResults as RuleResult[] ?? ruleResults.ToArray(); if (results.Any(x => !x.Success)) { @@ -180,7 +70,7 @@ namespace Ombi.Core.Engine foreach (var existingSeason in existingRequest.ChildRequests) foreach (var existing in existingSeason.SeasonRequests) { - var newChild = childRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == existing.SeasonNumber); + var newChild = tvBuilder.ChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == existing.SeasonNumber); if (newChild != null) { // We have some requests in this season... @@ -197,32 +87,19 @@ namespace Ombi.Core.Engine if (!newChild.Episodes.Any()) { // We may have removed all episodes - childRequest.SeasonRequests.Remove(newChild); + tvBuilder.ChildRequest.SeasonRequests.Remove(newChild); } } } // Remove the ID since this is a new child - childRequest.Id = 0; - return await AddExistingRequest(childRequest, existingRequest); + tvBuilder.ChildRequest.Id = 0; + return await AddExistingRequest(tvBuilder.ChildRequest, existingRequest); } // This is a new request - var model = new TvRequests - { - Id = tv.Id, - Overview = showInfo.summary.RemoveHtml(), - PosterPath = posterPath, - Title = showInfo.name, - ReleaseDate = firstAir, - Status = showInfo.status, - ImdbId = showInfo.externals?.imdb ?? string.Empty, - TvDbId = tv.Id, - ChildRequests = new List(), - TotalSeasons = tv.SeasonRequests.Count() - }; - model.ChildRequests.Add(childRequest); - return await AddRequest(model); + var newRequest = tvBuilder.CreateNewRequest(tv); + return await AddRequest(newRequest.NewRequest); } public async Task> GetRequests(int count, int position) diff --git a/src/Ombi.Core/NotificationHelper.cs b/src/Ombi.Core/Helpers/NotificationHelper.cs similarity index 100% rename from src/Ombi.Core/NotificationHelper.cs rename to src/Ombi.Core/Helpers/NotificationHelper.cs diff --git a/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs new file mode 100644 index 000000000..3f28d6ff2 --- /dev/null +++ b/src/Ombi.Core/Helpers/TvShowRequestBuilder.cs @@ -0,0 +1,197 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ombi.Api.TvMaze; +using Ombi.Api.TvMaze.Models; +using Ombi.Core.Models.Search; +using Ombi.Helpers; +using Ombi.Store.Entities; +using Ombi.Store.Entities.Requests; +using Ombi.Store.Repository.Requests; + +namespace Ombi.Core.Helpers +{ + public class TvShowRequestBuilder + { + + public TvShowRequestBuilder(ITvMazeApi tvApi) + { + TvApi = tvApi; + } + + private ITvMazeApi TvApi { get; } + + public ChildRequests ChildRequest { get; set; } + public List TvRequests { get; protected set; } + public string PosterPath { get; protected set; } + public DateTime FirstAir { get; protected set; } + public TvRequests NewRequest { get; protected set; } + protected TvMazeShow ShowInfo { get; set; } + + public async Task GetShowInfo(int id) + { + ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); + + DateTime.TryParse(ShowInfo.premiered, out DateTime dt); + + FirstAir = dt; + + // For some reason the poster path is always http + PosterPath = ShowInfo.image?.medium.Replace("http:", "https:"); + + return this; + } + + public TvShowRequestBuilder CreateChild(SearchTvShowViewModel model, int userId) + { + ChildRequest = new ChildRequests + { + Id = model.Id, + RequestType = RequestType.TvShow, + RequestedDate = DateTime.UtcNow, + Approved = false, + RequestedUserId = userId, + SeasonRequests = new List() + }; + + return this; + } + + public TvShowRequestBuilder CreateTvList(SearchTvShowViewModel tv) + { + TvRequests = new List(); + // Only have the TV requests we actually requested and not everything + foreach (var season in tv.SeasonRequests) + { + for (int i = season.Episodes.Count - 1; i >= 0; i--) + { + if (!season.Episodes[i].Requested) + { + season.Episodes.RemoveAt(i); // Remove the episode since it's not requested + } + } + + if (season.Episodes.Any()) + { + TvRequests.Add(season); + } + } + + return this; + + } + + + public async Task BuildEpisodes(SearchTvShowViewModel tv) + { + if (tv.RequestAll) + { + var episodes = await TvApi.EpisodeLookup(ShowInfo.id); + foreach (var ep in episodes) + { + var season = ChildRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season); + if (season == null) + { + ChildRequest.SeasonRequests.Add(new SeasonRequests + { + Episodes = new List{ + new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = DateTime.Parse(ep.airdate), + Title = ep.name, + Url = ep.url + } + }, + SeasonNumber = ep.season, + }); + } + else + { + season.Episodes.Add(new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = DateTime.Parse(ep.airdate), + Title = ep.name, + Url = ep.url + }); + } + } + + } + else if (tv.LatestSeason) + { + var episodes = await TvApi.EpisodeLookup(ShowInfo.id); + var latest = episodes.OrderBy(x => x.season).FirstOrDefault(); + var episodesRequests = new List(); + foreach (var ep in episodes) + { + episodesRequests.Add(new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = DateTime.Parse(ep.airdate), + Title = ep.name, + Url = ep.url + }); + } + ChildRequest.SeasonRequests.Add(new SeasonRequests + { + Episodes = episodesRequests, + SeasonNumber = latest.season, + }); + } + else if (tv.FirstSeason) + { + var episodes = await TvApi.EpisodeLookup(ShowInfo.id); + var first = episodes.OrderByDescending(x => x.season).FirstOrDefault(); + var episodesRequests = new List(); + foreach (var ep in episodes) + { + if (ep.season == first.season) + { + episodesRequests.Add(new EpisodeRequests + { + EpisodeNumber = ep.number, + AirDate = DateTime.Parse(ep.airdate), + Title = ep.name, + Url = ep.url + }); + } + } + ChildRequest.SeasonRequests.Add(new SeasonRequests + { + Episodes = episodesRequests, + SeasonNumber = first.season, + }); + } + else + { + // It's a custom request + ChildRequest.SeasonRequests = TvRequests; + } + return this; + } + + + public TvShowRequestBuilder CreateNewRequest(SearchTvShowViewModel tv) + { + NewRequest = new TvRequests + { + Id = tv.Id, + Overview = ShowInfo.summary.RemoveHtml(), + PosterPath = PosterPath, + Title = ShowInfo.name, + ReleaseDate = FirstAir, + Status = ShowInfo.status, + ImdbId = ShowInfo.externals?.imdb ?? string.Empty, + TvDbId = tv.Id, + ChildRequests = new List(), + TotalSeasons = tv.SeasonRequests.Count() + }; + NewRequest.ChildRequests.Add(ChildRequest); + + return this; + } + } +} \ No newline at end of file diff --git a/src/Ombi.Core/IMovieSender.cs b/src/Ombi.Core/Senders/IMovieSender.cs similarity index 100% rename from src/Ombi.Core/IMovieSender.cs rename to src/Ombi.Core/Senders/IMovieSender.cs diff --git a/src/Ombi.Core/INotificationHelper.cs b/src/Ombi.Core/Senders/INotificationHelper.cs similarity index 100% rename from src/Ombi.Core/INotificationHelper.cs rename to src/Ombi.Core/Senders/INotificationHelper.cs diff --git a/src/Ombi.Core/ITvSender.cs b/src/Ombi.Core/Senders/ITvSender.cs similarity index 100% rename from src/Ombi.Core/ITvSender.cs rename to src/Ombi.Core/Senders/ITvSender.cs diff --git a/src/Ombi.Core/MovieSender.cs b/src/Ombi.Core/Senders/MovieSender.cs similarity index 100% rename from src/Ombi.Core/MovieSender.cs rename to src/Ombi.Core/Senders/MovieSender.cs diff --git a/src/Ombi.Core/MovieSenderResult.cs b/src/Ombi.Core/Senders/MovieSenderResult.cs similarity index 100% rename from src/Ombi.Core/MovieSenderResult.cs rename to src/Ombi.Core/Senders/MovieSenderResult.cs diff --git a/src/Ombi.Core/TvSender.cs b/src/Ombi.Core/Senders/TvSender.cs similarity index 100% rename from src/Ombi.Core/TvSender.cs rename to src/Ombi.Core/Senders/TvSender.cs diff --git a/src/Ombi.Notifications.Tests/NotificationMessageResolverTests.cs b/src/Ombi.Notifications.Tests/NotificationMessageResolverTests.cs new file mode 100644 index 000000000..b3b66de43 --- /dev/null +++ b/src/Ombi.Notifications.Tests/NotificationMessageResolverTests.cs @@ -0,0 +1,32 @@ +//using System.Linq; +//using Ombi.Store.Entities; +//using Xunit; +//using Xunit.Abstractions; + +//namespace Ombi.Notifications.Tests +//{ +// public class NotificationMessageResolverTests +// { +// public NotificationMessageResolverTests(ITestOutputHelper helper) +// { +// _resolver = new NotificationMessageResolver(); +// output = helper; +// } + +// private readonly NotificationMessageResolver _resolver; +// private readonly ITestOutputHelper output; + +// [Fact] +// public void Resolved_ShouldResolve_RequestedUser() +// { +// var result = _resolver.ParseMessage(new NotificationTemplates +// { +// Subject = "This is a {RequestedUser}" +// }, new NotificationMessageCurlys {RequestedUser = "Abc"}); +// output.WriteLine(result.Message); +// //Assert.True(result.Message.Equals("This is a Abc")); + +// Assert.Contains("11a", result.Message); +// } +// } +//} diff --git a/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj b/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj new file mode 100644 index 000000000..d6b0a4d1a --- /dev/null +++ b/src/Ombi.Notifications.Tests/Ombi.Notifications.Tests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp1.1 + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Ombi.Notifications/NotificationMessageContent.cs b/src/Ombi.Notifications/NotificationMessageContent.cs new file mode 100644 index 000000000..e70b88072 --- /dev/null +++ b/src/Ombi.Notifications/NotificationMessageContent.cs @@ -0,0 +1,9 @@ +namespace Ombi.Notifications +{ + public class NotificationMessageContent + { + public string Subject { get; set; } + public string Message { get; set; } + public string Image { get; set; } + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications/NotificationMessageCurlys.cs b/src/Ombi.Notifications/NotificationMessageCurlys.cs new file mode 100644 index 000000000..d41036e81 --- /dev/null +++ b/src/Ombi.Notifications/NotificationMessageCurlys.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using Ombi.Store.Entities.Requests; + +namespace Ombi.Notifications +{ + public class NotificationMessageCurlys + { + + public void Setup(FullBaseRequest req) + { + RequestedUser = string.IsNullOrEmpty(req.RequestedUser.Alias) + ? req.RequestedUser.Username + : req.RequestedUser.Alias; + Title = req.Title; + RequestedDate = req.RequestedDate.ToString("D"); + Type = req.RequestType.ToString(); + Overview = req.Overview; + Year = req.ReleaseDate.Year.ToString(); + PosterImage = req.PosterPath; + } + + public void Setup(ChildRequests req) + { + RequestedUser = string.IsNullOrEmpty(req.RequestedUser.Alias) + ? req.RequestedUser.Username + : req.RequestedUser.Alias; + Title = req.ParentRequest.Title; + RequestedDate = req.RequestedDate.ToString("D"); + Type = req.RequestType.ToString(); + Overview = req.ParentRequest.Overview; + Year = req.ParentRequest.ReleaseDate.Year.ToString(); + PosterImage = req.ParentRequest.PosterPath; + // DO Episode and Season Lists + } + + // User Defined + public string RequestedUser { get; set; } + public string Title { get; set; } + public string RequestedDate { get; set; } + public string Type { get; set; } + public string Issue { get; set; } + public string Overview { get; set; } + public string Year { get; set; } + public string EpisodesList { get; set; } + public string SeasonsList { get; set; } + public string PosterImage { get; set; } + + // System Defined + private string LongDate => DateTime.Now.ToString("D"); + private string ShortDate => DateTime.Now.ToString("d"); + private string LongTime => DateTime.Now.ToString("T"); + private string ShortTime => DateTime.Now.ToString("t"); + + public Dictionary Curlys => new Dictionary + { + {nameof(RequestedUser), RequestedUser }, + {nameof(Title), Title }, + {nameof(RequestedDate), RequestedDate }, + {nameof(Type), Type }, + {nameof(Issue), Issue }, + {nameof(LongDate),LongDate}, + {nameof(ShortDate),ShortDate}, + {nameof(LongTime),LongTime}, + {nameof(ShortTime),ShortTime}, + {nameof(Overview),Overview}, + {nameof(Year),Year}, + {nameof(EpisodesList),EpisodesList}, + {nameof(SeasonsList),SeasonsList}, + {nameof(PosterImage),PosterImage}, + }; + } +} \ No newline at end of file diff --git a/src/Ombi.Notifications/NotificationMessageResolver.cs b/src/Ombi.Notifications/NotificationMessageResolver.cs index 371a79583..451ef1b55 100644 --- a/src/Ombi.Notifications/NotificationMessageResolver.cs +++ b/src/Ombi.Notifications/NotificationMessageResolver.cs @@ -1,84 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; using Ombi.Store.Entities; -using Ombi.Store.Entities.Requests; namespace Ombi.Notifications { - public class NotificationMessageContent - { - public string Subject { get; set; } - public string Message { get; set; } - public string Image { get; set; } - } - public class NotificationMessageCurlys - { - - public void Setup(FullBaseRequest req) - { - RequestedUser = string.IsNullOrEmpty(req.RequestedUser.Alias) - ? req.RequestedUser.Username - : req.RequestedUser.Alias; - Title = req.Title; - RequestedDate = req.RequestedDate.ToString("D"); - Type = req.RequestType.ToString(); - Overview = req.Overview; - Year = req.ReleaseDate.Year.ToString(); - PosterImage = req.PosterPath; - } - - public void Setup(ChildRequests req) - { - RequestedUser = string.IsNullOrEmpty(req.RequestedUser.Alias) - ? req.RequestedUser.Username - : req.RequestedUser.Alias; - Title = req.ParentRequest.Title; - RequestedDate = req.RequestedDate.ToString("D"); - Type = req.RequestType.ToString(); - Overview = req.ParentRequest.Overview; - Year = req.ParentRequest.ReleaseDate.Year.ToString(); - PosterImage = req.ParentRequest.PosterPath; - // DO Episode and Season Lists - } - - // User Defined - public string RequestedUser { get; set; } - public string Title { get; set; } - public string RequestedDate { get; set; } - public string Type { get; set; } - public string Issue { get; set; } - public string Overview { get; set; } - public string Year { get; set; } - public string EpisodesList { get; set; } - public string SeasonsList { get; set; } - public string PosterImage { get; set; } - - // System Defined - private string LongDate => DateTime.Now.ToString("D"); - private string ShortDate => DateTime.Now.ToString("d"); - private string LongTime => DateTime.Now.ToString("T"); - private string ShortTime => DateTime.Now.ToString("t"); - - public Dictionary Curlys => new Dictionary - { - {nameof(RequestedUser), RequestedUser }, - {nameof(Title), Title }, - {nameof(RequestedDate), RequestedDate }, - {nameof(Type), Type }, - {nameof(Issue), Issue }, - {nameof(LongDate),LongDate}, - {nameof(ShortDate),ShortDate}, - {nameof(LongTime),LongTime}, - {nameof(ShortTime),ShortTime}, - {nameof(Overview),Overview}, - {nameof(Year),Year}, - {nameof(EpisodesList),EpisodesList}, - {nameof(SeasonsList),SeasonsList}, - {nameof(PosterImage),PosterImage}, - }; - } - public class NotificationMessageResolver { /// @@ -113,20 +39,99 @@ namespace Ombi.Notifications private NotificationMessageContent Resolve(string body, string subject, IReadOnlyDictionary parameters) { // Find the fields - var bodyFields = FindCurlyFields(body); - var subjectFields = FindCurlyFields(subject); + var bodyFields = FindFields(body, StartChar, EndChar); + var subjectFields = FindFields(subject, StartChar, EndChar); + + //var conditionalFields = FindFields(body, '<', '>'); + //ProcessConditions(conditionalFields, parameters); body = ReplaceFields(bodyFields, parameters, body); subject = ReplaceFields(subjectFields, parameters, subject); return new NotificationMessageContent { Message = body ?? string.Empty, Subject = subject ?? string.Empty}; } + public IEnumerable ProcessConditions(IEnumerable conditionalFields, IReadOnlyDictionary parameters) + { + foreach (var f in conditionalFields) + { + var field = f.ToLower(); + if (field.StartsWith("if")) + { + var ifPosition = field.IndexOf("if", StringComparison.Ordinal); + Console.WriteLine(ifPosition); + var identifierStart = field.Substring(ifPosition + 3); + Console.WriteLine(identifierStart); + var identifierEnd = identifierStart.IndexOf(' '); + Console.WriteLine(identifierEnd); + + var identitifier = identifierStart.Substring(ifPosition, identifierEnd); + + if (identitifier.Equals("type")) + { + // Find the operator == or != + var stringWithoutIdentifier = identifierStart.Substring(identitifier.Length + 1); + var operatorValue = stringWithoutIdentifier.Substring(0,2); + + var stringWithoutOperator = stringWithoutIdentifier.Substring(operatorValue.Length + 1); + var endPosition = stringWithoutOperator.IndexOf(' '); + var comparison = stringWithoutOperator.Substring(0, endPosition); + + if (operatorValue == "==") + { + var type = (RequestType)int.Parse(parameters["Type"]); + if (comparison.Equals("Movie", StringComparison.CurrentCultureIgnoreCase)) + { + if (type == RequestType.Movie) + { + // Get the text + var stringWithoutComparison = stringWithoutOperator.Substring(comparison.Length + 2); + var endString = stringWithoutComparison.IndexOf(' '); + var text = stringWithoutComparison.Substring(0, endString - 1); + field = text; + } + else + { + // Get the text in the ELSE + var stringWithoutComparison = stringWithoutOperator.Substring(comparison.Length + 2); + var elseIndex = stringWithoutComparison.IndexOf("else", StringComparison.CurrentCultureIgnoreCase); + var endIndex = stringWithoutComparison.IndexOf(' '); + if (elseIndex >= 0) + { + var elseString = stringWithoutComparison.Substring(elseIndex, endIndex); + + } + else + { + // No else + } + } + } + else if(comparison.Equals("TvShow", StringComparison.CurrentCultureIgnoreCase) || comparison.Equals("Tv", StringComparison.CurrentCultureIgnoreCase)) + { + if (type == RequestType.TvShow) + { + + } + } + } + else if (operatorValue == "!=") + { + + } + } + + } + } + + return conditionalFields; + } + /// /// Finds the curly fields. /// /// The message. /// - private IEnumerable FindCurlyFields(string message) + private IEnumerable FindFields(string message, char start, char end) { if (string.IsNullOrEmpty(message)) { @@ -145,13 +150,13 @@ namespace Ombi.Notifications continue; } - if (c == StartChar) // Start of curly '{' + if (c == start) // Start of curly '{' { insideCurly = true; continue; } - if (c == EndChar) // End of curly '}' + if (c == end) // End of curly '}' { fields.Add(currentWord); // We have finished the curly, add the word into the list currentWord = string.Empty; diff --git a/src/Ombi.sln b/src/Ombi.sln index f59909602..6d46fa167 100644 --- a/src/Ombi.sln +++ b/src/Ombi.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.6 +VisualStudioVersion = 15.0.26430.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj", "{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}" EndProject @@ -13,58 +13,60 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Build\publish.bat = Build\publish.bat EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core", "Ombi.Core\Ombi.Core.csproj", "{F56E79C7-791D-4668-A0EC-29E3BBC8D24B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Core", "Ombi.Core\Ombi.Core.csproj", "{F56E79C7-791D-4668-A0EC-29E3BBC8D24B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Api", "Api", "{9293CA11-360A-4C20-A674-B9E794431BF5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.TheMovieDb", "Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj", "{132DA282-5894-4570-8916-D8C18ED2CE84}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.TheMovieDb", "Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj", "{132DA282-5894-4570-8916-D8C18ED2CE84}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api", "Ombi.Api\Ombi.Api.csproj", "{EA31F915-31F9-4318-B521-1500CDF40DDF}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api", "Ombi.Api\Ombi.Api.csproj", "{EA31F915-31F9-4318-B521-1500CDF40DDF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers", "Ombi.Helpers\Ombi.Helpers.csproj", "{C182B435-1FAB-49C5-9BF9-F7F5C6EF3C94}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Store", "Ombi.Store\Ombi.Store.csproj", "{68086581-1EFD-4390-8100-47F87D1CB628}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.DependencyInjection", "Ombi.DependencyInjection\Ombi.DependencyInjection.csproj", "{B39E4558-C557-48E7-AA74-19C5CD809617}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.DependencyInjection", "Ombi.DependencyInjection\Ombi.DependencyInjection.csproj", "{B39E4558-C557-48E7-AA74-19C5CD809617}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Mapping", "Ombi.Mapping\Ombi.Mapping.csproj", "{63E63511-1C7F-4162-8F92-8F7391B3C8A3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Mapping", "Ombi.Mapping\Ombi.Mapping.csproj", "{63E63511-1C7F-4162-8F92-8F7391B3C8A3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mappers", "Mappers", "{025FB189-2FFB-4F43-A64B-6F1B5A0D2065}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DI", "DI", "{410F36CF-9C60-428A-B191-6FD90610991A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Plex", "Ombi.Api.Plex\Ombi.Api.Plex.csproj", "{2E1A7B91-F29B-42BC-8F1E-1CF2DCC389BA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Plex", "Ombi.Api.Plex\Ombi.Api.Plex.csproj", "{2E1A7B91-F29B-42BC-8F1E-1CF2DCC389BA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Schedule", "Ombi.Schedule\Ombi.Schedule.csproj", "{5B42ADD4-757A-47C1-9CC5-320829C5E665}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Schedule", "Ombi.Schedule\Ombi.Schedule.csproj", "{5B42ADD4-757A-47C1-9CC5-320829C5E665}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Emby", "Ombi.Api.Emby\Ombi.Api.Emby.csproj", "{08FF107D-31E1-470D-AF86-E09B015CEE06}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Emby", "Ombi.Api.Emby\Ombi.Api.Emby.csproj", "{08FF107D-31E1-470D-AF86-E09B015CEE06}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Sonarr", "Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj", "{CFB5E008-D0D0-43C0-AA06-89E49D17F384}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Sonarr", "Ombi.Api.Sonarr\Ombi.Api.Sonarr.csproj", "{CFB5E008-D0D0-43C0-AA06-89E49D17F384}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6F42AB98-9196-44C4-B888-D5E409F415A1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.TvMaze", "Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj", "{0E8EF835-E4F0-4EE5-A2B6-678DEE973721}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.TvMaze", "Ombi.Api.TvMaze\Ombi.Api.TvMaze.csproj", "{0E8EF835-E4F0-4EE5-A2B6-678DEE973721}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notifications", "Ombi.Notifications\Ombi.Notifications.csproj", "{E6EE2830-E4AC-4F2E-AD93-2C9305605761}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Notifications", "Ombi.Notifications\Ombi.Notifications.csproj", "{E6EE2830-E4AC-4F2E-AD93-2C9305605761}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Notifications", "Notifications", "{EA30DD15-6280-4687-B370-2956EC2E54E5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notifications.Templates", "Ombi.Notifications.Templates\Ombi.Notifications.Templates.csproj", "{6EE01B17-0966-4E11-8BC1-A5318A92AB1D}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Notifications.Templates", "Ombi.Notifications.Templates\Ombi.Notifications.Templates.csproj", "{6EE01B17-0966-4E11-8BC1-A5318A92AB1D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Settings", "Ombi.Settings\Ombi.Settings.csproj", "{AE3AA23D-5B66-42AF-B44E-B9B4D8856C6F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Settings", "Ombi.Settings\Ombi.Settings.csproj", "{AE3AA23D-5B66-42AF-B44E-B9B4D8856C6F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Trakt", "Ombi.Api.Trakt\Ombi.Api.Trakt.csproj", "{3880375C-1A7E-4D75-96EC-63B954C42FEA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Trakt", "Ombi.Api.Trakt\Ombi.Api.Trakt.csproj", "{3880375C-1A7E-4D75-96EC-63B954C42FEA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core.Tests", "Ombi.Core.Tests\Ombi.Core.Tests.csproj", "{FC6A8F7C-9722-4AE4-960D-277ACB0E81CB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Core.Tests", "Ombi.Core.Tests\Ombi.Core.Tests.csproj", "{FC6A8F7C-9722-4AE4-960D-277ACB0E81CB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Radarr", "Ombi.Api.Radarr\Ombi.Api.Radarr.csproj", "{94D04C1F-E35A-499C-B0A0-9FADEBDF8336}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Radarr", "Ombi.Api.Radarr\Ombi.Api.Radarr.csproj", "{94D04C1F-E35A-499C-B0A0-9FADEBDF8336}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Discord", "Ombi.Api.Discord\Ombi.Api.Discord.csproj", "{5AF2B6D2-5CC6-49FE-928A-BA27AF52B194}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Updater", "Ombi.Updater\Ombi.Updater.csproj", "{6294A82D-4915-4FC3-B301-8F985716F34C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Updater", "Ombi.Updater\Ombi.Updater.csproj", "{6294A82D-4915-4FC3-B301-8F985716F34C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Update", "Update", "{D11FE57E-1E57-491D-A1D4-01AEF4BE5CB6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Notifications.Tests", "Ombi.Notifications.Tests\Ombi.Notifications.Tests.csproj", "{2C7836E7-B120-40A6-B641-DDAA02FBAE23}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -155,6 +157,10 @@ Global {6294A82D-4915-4FC3-B301-8F985716F34C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6294A82D-4915-4FC3-B301-8F985716F34C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6294A82D-4915-4FC3-B301-8F985716F34C}.Release|Any CPU.Build.0 = Release|Any CPU + {2C7836E7-B120-40A6-B641-DDAA02FBAE23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C7836E7-B120-40A6-B641-DDAA02FBAE23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C7836E7-B120-40A6-B641-DDAA02FBAE23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C7836E7-B120-40A6-B641-DDAA02FBAE23}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -175,5 +181,6 @@ Global {94D04C1F-E35A-499C-B0A0-9FADEBDF8336} = {9293CA11-360A-4C20-A674-B9E794431BF5} {5AF2B6D2-5CC6-49FE-928A-BA27AF52B194} = {9293CA11-360A-4C20-A674-B9E794431BF5} {6294A82D-4915-4FC3-B301-8F985716F34C} = {D11FE57E-1E57-491D-A1D4-01AEF4BE5CB6} + {2C7836E7-B120-40A6-B641-DDAA02FBAE23} = {6F42AB98-9196-44C4-B888-D5E409F415A1} EndGlobalSection EndGlobal diff --git a/src/Ombi/ClientApp/app/app.module.ts b/src/Ombi/ClientApp/app/app.module.ts index 91175e0f8..a547cd915 100644 --- a/src/Ombi/ClientApp/app/app.module.ts +++ b/src/Ombi/ClientApp/app/app.module.ts @@ -16,11 +16,8 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; // Components import { AppComponent } from './app.component'; + // Search -import { SearchComponent } from './search/search.component'; -import { MovieSearchComponent } from './search/moviesearch.component'; -import { TvSearchComponent } from './search/tvsearch.component'; -import { SeriesInformationComponent } from './search/seriesinformation.component'; // Request import { RequestComponent } from './requests/request.component'; @@ -32,10 +29,10 @@ import { RequestCardComponent } from './request-grid/request-card.component'; import { LoginComponent } from './login/login.component'; import { LandingPageComponent } from './landingpage/landingpage.component'; import { UserManagementComponent } from './usermanagement/usermanagement.component'; +import { UserManagementEditComponent } from './usermanagement/usermanagement-edit.component'; import { PageNotFoundComponent } from './errors/not-found.component'; // Services -import { SearchService } from './services/search.service'; import { RequestService } from './services/request.service'; import { NotificationService } from './services/notification.service'; import { SettingsService } from './services/settings.service'; @@ -49,17 +46,17 @@ import { StatusService } from './services/status.service'; // Modules import { SettingsModule } from './settings/settings.module'; import { WizardModule } from './wizard/wizard.module'; +import { SearchModule } from './search/search.module'; const routes: Routes = [ { path: '*', component: PageNotFoundComponent }, { path: '', redirectTo: '/search', pathMatch: 'full' }, - { path: 'search', component: SearchComponent, canActivate: [AuthGuard] }, - { path: 'search/show/:id', component: SeriesInformationComponent, canActivate: [AuthGuard] }, { path: 'requests', component: RequestComponent, canActivate: [AuthGuard] }, //{ path: 'requests-grid', component: RequestGridComponent }, { path: 'login', component: LoginComponent }, { path: 'landingpage', component: LandingPageComponent }, { path: 'usermanagement', component: UserManagementComponent, canActivate: [AuthGuard] }, + { path: 'usermanagement/edit/:id', component: UserManagementEditComponent, canActivate: [AuthGuard] }, ]; @NgModule({ @@ -77,6 +74,7 @@ const routes: Routes = [ InfiniteScrollModule, AuthModule, WizardModule, + SearchModule, DialogModule, MdButtonModule, NgbModule.forRoot(), @@ -89,21 +87,17 @@ const routes: Routes = [ declarations: [ AppComponent, PageNotFoundComponent, - SearchComponent, RequestComponent, LoginComponent, - MovieSearchComponent, - TvSearchComponent, LandingPageComponent, UserManagementComponent, MovieRequestsComponent, TvRequestsComponent, - SeriesInformationComponent, //RequestGridComponent, RequestCardComponent, + UserManagementEditComponent, ], providers: [ - SearchService, RequestService, NotificationService, AuthService, diff --git a/src/Ombi/ClientApp/app/login/login.component.html b/src/Ombi/ClientApp/app/login/login.component.html index e44985851..2104443a0 100644 --- a/src/Ombi/ClientApp/app/login/login.component.html +++ b/src/Ombi/ClientApp/app/login/login.component.html @@ -2,11 +2,11 @@ you can substitue the span of reauth email for a input with the email and include the remember me checkbox --> -
+
-
+

diff --git a/src/Ombi/ClientApp/app/search/search.module.ts b/src/Ombi/ClientApp/app/search/search.module.ts new file mode 100644 index 000000000..a1baa0bbe --- /dev/null +++ b/src/Ombi/ClientApp/app/search/search.module.ts @@ -0,0 +1,45 @@ +import { NgModule, } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule, Routes } from '@angular/router'; + +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { SearchComponent } from './search.component'; +import { MovieSearchComponent } from './moviesearch.component'; +import { TvSearchComponent } from './tvsearch.component'; +import { SeriesInformationComponent } from './seriesinformation.component'; + +import { SearchService } from '../services/search.service'; +import { RequestService } from '../services/request.service'; + +import { AuthGuard } from '../auth/auth.guard'; + +const routes: Routes = [ + { path: 'search', component: SearchComponent, canActivate: [AuthGuard] }, + { path: 'search/show/:id', component: SeriesInformationComponent, canActivate: [AuthGuard] }, +]; + +@NgModule({ + imports: [ + CommonModule, + FormsModule, + RouterModule.forChild(routes), + NgbModule.forRoot(), + ], + declarations: [ + SearchComponent, + MovieSearchComponent, + TvSearchComponent, + SeriesInformationComponent + ], + exports: [ + RouterModule + ], + providers: [ + SearchService, + RequestService + ], + +}) +export class SearchModule { } \ No newline at end of file diff --git a/src/Ombi/ClientApp/app/services/identity.service.ts b/src/Ombi/ClientApp/app/services/identity.service.ts index 16c65d816..4d499b5e9 100644 --- a/src/Ombi/ClientApp/app/services/identity.service.ts +++ b/src/Ombi/ClientApp/app/services/identity.service.ts @@ -20,6 +20,10 @@ export class IdentityService extends ServiceAuthHelpers { return this.http.get(this.url).map(this.extractData); } + getUserById(id: number): Observable { + return this.http.get(`${this.url}User/${id}`).map(this.extractData); + } + getUsers(): Observable { return this.http.get(`${this.url}Users`).map(this.extractData); } diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html new file mode 100644 index 000000000..5aee0ebf6 --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.html @@ -0,0 +1,53 @@ +

User Management

+ + + + +
+ +
diff --git a/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts new file mode 100644 index 000000000..3ec116c3e --- /dev/null +++ b/src/Ombi/ClientApp/app/usermanagement/usermanagement-edit.component.ts @@ -0,0 +1,31 @@ +import { Component, OnInit } from '@angular/core'; + +import { IUser, ICheckbox } from '../interfaces/IUser'; +import { IdentityService } from '../services/identity.service'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + + templateUrl: './usermanagement-edit.component.html' +}) +export class UserManagementEditComponent implements OnInit { + constructor(private identityService: IdentityService, private route: ActivatedRoute) { + this.route.params + .subscribe(params => { + this.userId = +params['id']; // (+) converts string 'id' to a number + }); + } + + ngOnInit(): void { + + this.identityService.getAllAvailableClaims().subscribe(x => this.availableClaims = x); + this.identityService.getUserById(this.userId).subscribe(x => this.user = x); + } + + user: IUser; + userId: number; + + + availableClaims : ICheckbox[]; + +} \ No newline at end of file diff --git a/src/Ombi/Controllers/IdentityController.cs b/src/Ombi/Controllers/IdentityController.cs index dd913a05a..306b9ee6f 100644 --- a/src/Ombi/Controllers/IdentityController.cs +++ b/src/Ombi/Controllers/IdentityController.cs @@ -112,7 +112,7 @@ namespace Ombi.Controllers /// Gets the user by the user id. ///
/// Information about the user - [HttpGet("Users/{id}")] + [HttpGet("User/{id}")] public async Task GetUser(int id) { var type = typeof(OmbiClaims); @@ -121,7 +121,7 @@ namespace Ombi.Controllers var fields = fieldInfos.Where(fi => fi.IsLiteral && !fi.IsInitOnly).ToList(); var allClaims = fields.Select(x => x.Name).ToList(); - var user = Mapper.Map(await IdentityManager.GetUser(id)).ToList(); + var user = Mapper.Map(await IdentityManager.GetUser(id)); var userClaims = user.Claims.Select(x => x.Value); diff --git a/src/Ombi/wwwroot/images/ms-icon-150x150.png b/src/Ombi/wwwroot/images/ms-icon-150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..33ba502216b63e46e1cd60b61d4ee1cdf9382671 GIT binary patch literal 12016 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 literal 0 HcmV?d00001 diff --git a/src/Ombi/wwwroot/images/ms-icon-310x310.png b/src/Ombi/wwwroot/images/ms-icon-310x310.png new file mode 100644 index 0000000000000000000000000000000000000000..a2044bbb1840f683766aa3f90ff3929723d4d368 GIT binary patch literal 36529 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 RIzSs3JYD@<);T3K0RRbn^&9{I literal 0 HcmV?d00001