mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-11 15:56:05 -07:00
Add the Issue Reporting functionality (#1811)
* Added issuesreporting and the ability to add categories to the UI * Added lazy loading!
This commit is contained in:
parent
438f56eceb
commit
246f1c07cf
109 changed files with 2905 additions and 526 deletions
11
build.cake
11
build.cake
|
@ -95,6 +95,10 @@ Task("SetVersionInfo")
|
|||
{
|
||||
fullVer = buildVersion + "-PR";
|
||||
}
|
||||
if(fullVer.Contains("_"))
|
||||
{
|
||||
fullVer = fullVer.Replace("_","");
|
||||
}
|
||||
|
||||
buildSettings.ArgumentCustomization = args => args.Append("/p:SemVer=" + versionInfo.AssemblySemVer);
|
||||
buildSettings.ArgumentCustomization = args => args.Append("/p:FullVer=" + fullVer);
|
||||
|
@ -154,7 +158,6 @@ Task("Package")
|
|||
});
|
||||
|
||||
Task("Publish")
|
||||
.IsDependentOn("Run-Unit-Tests")
|
||||
.IsDependentOn("PrePublish")
|
||||
.IsDependentOn("Publish-Windows")
|
||||
.IsDependentOn("Publish-OSX").IsDependentOn("Publish-Linux")
|
||||
|
@ -204,12 +207,6 @@ Task("Publish-Linux")
|
|||
DotNetCorePublish("./src/Ombi.Updater/Ombi.Updater.csproj", publishSettings);
|
||||
});
|
||||
|
||||
Task("Run-Unit-Tests")
|
||||
.Does(() =>
|
||||
{
|
||||
DotNetCoreBuild(csProj, buildSettings);
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// TASK TARGETS
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Ombi.Api
|
|||
{
|
||||
public class Api : IApi
|
||||
{
|
||||
public Api(ILogger<Api> log, ISettingsService<OmbiSettings> s, IMemoryCache cache)
|
||||
public Api(ILogger<Api> log, ISettingsService<OmbiSettings> s, ICacheService cache)
|
||||
{
|
||||
Logger = log;
|
||||
_settings = s;
|
||||
|
@ -25,15 +25,11 @@ namespace Ombi.Api
|
|||
|
||||
private ILogger<Api> Logger { get; }
|
||||
private readonly ISettingsService<OmbiSettings> _settings;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ICacheService _cache;
|
||||
|
||||
private async Task<HttpMessageHandler> GetHandler()
|
||||
{
|
||||
var settings = await _cache.GetOrCreateAsync(CacheKeys.OmbiSettings, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
|
||||
return await _settings.GetSettingsAsync();
|
||||
});
|
||||
var settings = await _cache.GetOrAdd(CacheKeys.OmbiSettings, async () => await _settings.GetSettingsAsync(), DateTime.Now.AddHours(1));
|
||||
if (settings.IgnoreCertificateErrors)
|
||||
{
|
||||
return new HttpClientHandler
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Ombi.Core.Engine
|
|||
public class MovieSearchEngine : BaseMediaEngine, IMovieEngine
|
||||
{
|
||||
public MovieSearchEngine(IPrincipal identity, IRequestServiceMain service, IMovieDbApi movApi, IMapper mapper,
|
||||
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, IMemoryCache mem)
|
||||
ILogger<MovieSearchEngine> logger, IRuleEvaluator r, OmbiUserManager um, ICacheService mem)
|
||||
: base(identity, service, r, um)
|
||||
{
|
||||
MovieApi = movApi;
|
||||
|
@ -31,7 +31,7 @@ namespace Ombi.Core.Engine
|
|||
private IMovieDbApi MovieApi { get; }
|
||||
private IMapper Mapper { get; }
|
||||
private ILogger<MovieSearchEngine> Logger { get; }
|
||||
private IMemoryCache MemCache { get; }
|
||||
private ICacheService MemCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Lookups the imdb information.
|
||||
|
@ -69,11 +69,7 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.PopularMovies, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await MovieApi.PopularMovies();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.PopularMovies, async () => await MovieApi.PopularMovies(), DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
Logger.LogDebug("Search Result: {result}", result);
|
||||
|
@ -88,11 +84,7 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.TopRatedMovies, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await MovieApi.TopRated();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.TopRatedMovies, async () => await MovieApi.TopRated(), DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
Logger.LogDebug("Search Result: {result}", result);
|
||||
|
@ -107,11 +99,7 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.UpcomingMovies, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await MovieApi.Upcoming();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.UpcomingMovies, async () => await MovieApi.Upcoming(), DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
Logger.LogDebug("Search Result: {result}", result);
|
||||
|
@ -126,11 +114,7 @@ namespace Ombi.Core.Engine
|
|||
/// <returns></returns>
|
||||
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.NowPlayingMovies, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await MovieApi.NowPlaying();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.NowPlayingMovies, async () => await MovieApi.NowPlaying(), DateTime.Now.AddHours(12));
|
||||
if (result != null)
|
||||
{
|
||||
Logger.LogDebug("Search Result: {result}", result);
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Ombi.Core.Engine
|
|||
{
|
||||
public TvSearchEngine(IPrincipal identity, IRequestServiceMain service, ITvMazeApi tvMaze, IMapper mapper, ISettingsService<PlexSettings> plexSettings,
|
||||
ISettingsService<EmbySettings> embySettings, IPlexContentRepository repo, IEmbyContentRepository embyRepo, ITraktApi trakt, IRuleEvaluator r, OmbiUserManager um,
|
||||
IMemoryCache memCache)
|
||||
ICacheService memCache)
|
||||
: base(identity, service, r, um)
|
||||
{
|
||||
TvMazeApi = tvMaze;
|
||||
|
@ -46,7 +46,7 @@ namespace Ombi.Core.Engine
|
|||
private IPlexContentRepository PlexContentRepo { get; }
|
||||
private IEmbyContentRepository EmbyContentRepo { get; }
|
||||
private ITraktApi TraktApi { get; }
|
||||
private IMemoryCache MemCache { get; }
|
||||
private ICacheService MemCache { get; }
|
||||
|
||||
public async Task<IEnumerable<SearchTvShowViewModel>> Search(string searchTerm)
|
||||
{
|
||||
|
@ -124,44 +124,28 @@ namespace Ombi.Core.Engine
|
|||
|
||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Popular()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.PopularTv, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await TraktApi.GetPopularShows();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.PopularTv, async () => await TraktApi.GetPopularShows(), DateTime.Now.AddHours(12));
|
||||
var processed = await ProcessResults(result);
|
||||
return processed.Select(ParseIntoTreeNode).ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Anticipated()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.AnticipatedTv, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await TraktApi.GetAnticipatedShows();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.AnticipatedTv, async () => await TraktApi.GetAnticipatedShows(), DateTime.Now.AddHours(12));
|
||||
var processed= await ProcessResults(result);
|
||||
return processed.Select(ParseIntoTreeNode).ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> MostWatches()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.MostWatchesTv, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await TraktApi.GetMostWatchesShows();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.MostWatchesTv, async () => await TraktApi.GetMostWatchesShows(), DateTime.Now.AddHours(12));
|
||||
var processed = await ProcessResults(result);
|
||||
return processed.Select(ParseIntoTreeNode).ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<TreeNode<SearchTvShowViewModel>>> Trending()
|
||||
{
|
||||
var result = await MemCache.GetOrCreateAsync(CacheKeys.TrendingTv, async entry =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(12);
|
||||
return await TraktApi.GetTrendingShows();
|
||||
});
|
||||
var result = await MemCache.GetOrAdd(CacheKeys.TrendingTv, async () => await TraktApi.GetTrendingShows(), DateTime.Now.AddHours(12));
|
||||
var processed = await ProcessResults(result);
|
||||
return processed.Select(ParseIntoTreeNode).ToList();
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ namespace Ombi.Core
|
|||
RequestType = model.RequestType,
|
||||
Recipient = model.RequestedUser?.Email ?? string.Empty
|
||||
};
|
||||
|
||||
BackgroundJob.Enqueue(() => NotificationService.Publish(notificationModel));
|
||||
}
|
||||
public void Notify(ChildRequests model, NotificationType type)
|
||||
|
|
|
@ -52,7 +52,8 @@ namespace Ombi.Core.Helpers
|
|||
RequestedDate = DateTime.UtcNow,
|
||||
Approved = false,
|
||||
RequestedUserId = userId,
|
||||
SeasonRequests = new List<SeasonRequests>()
|
||||
SeasonRequests = new List<SeasonRequests>(),
|
||||
Title = model.Title
|
||||
};
|
||||
|
||||
return this;
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
namespace Ombi.Core.Models.Requests
|
||||
{
|
||||
public enum IssueState
|
||||
{
|
||||
None = 99,
|
||||
WrongAudio = 0,
|
||||
NoSubtitles = 1,
|
||||
WrongContent = 2,
|
||||
PlaybackIssues = 3,
|
||||
Other = 4 // Provide a message
|
||||
}
|
||||
}
|
|
@ -83,7 +83,10 @@ namespace Ombi.Core.Senders
|
|||
Success = true
|
||||
};
|
||||
}
|
||||
return new SenderResult();
|
||||
return new SenderResult
|
||||
{
|
||||
Message = "Could not send to SickRage!"
|
||||
};
|
||||
}
|
||||
return new SenderResult
|
||||
{
|
||||
|
|
|
@ -128,9 +128,10 @@ namespace Ombi.DependencyInjection
|
|||
public static void RegisterServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<IRequestServiceMain, RequestService>();
|
||||
services.AddSingleton<INotificationService, NotificationService>();
|
||||
services.AddSingleton<IEmailProvider, GenericEmailProvider>();
|
||||
services.AddTransient<INotificationService, NotificationService>();
|
||||
services.AddTransient<IEmailProvider, GenericEmailProvider>();
|
||||
services.AddTransient<INotificationHelper, NotificationHelper>();
|
||||
services.AddTransient<ICacheService, CacheService>();
|
||||
|
||||
services.AddTransient<IDiscordNotification, DiscordNotification>();
|
||||
services.AddTransient<IEmailNotification, EmailNotification>();
|
||||
|
@ -140,7 +141,6 @@ namespace Ombi.DependencyInjection
|
|||
services.AddTransient<IMattermostNotification, MattermostNotification>();
|
||||
services.AddTransient<IPushoverNotification, PushoverNotification>();
|
||||
services.AddTransient<ITelegramNotification, TelegramNotification>();
|
||||
|
||||
}
|
||||
|
||||
public static void RegisterJobs(this IServiceCollection services)
|
||||
|
|
80
src/Ombi.Helpers/CacheService.cs
Normal file
80
src/Ombi.Helpers/CacheService.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Nito.AsyncEx;
|
||||
|
||||
namespace Ombi.Helpers
|
||||
{
|
||||
public class CacheService : ICacheService
|
||||
{
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly AsyncLock _mutex = new AsyncLock();
|
||||
public CacheService(IMemoryCache memoryCache)
|
||||
{
|
||||
_memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
|
||||
}
|
||||
|
||||
public async Task<T> GetOrAdd<T>(string cacheKey, Func<Task<T>> factory, DateTime absoluteExpiration = default(DateTime))
|
||||
{
|
||||
if (absoluteExpiration == default(DateTime))
|
||||
{
|
||||
absoluteExpiration = DateTime.Now.AddHours(1);
|
||||
}
|
||||
// locks get and set internally
|
||||
if (_memoryCache.TryGetValue<T>(cacheKey, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
using (await _mutex.LockAsync())
|
||||
{
|
||||
if (_memoryCache.TryGetValue(cacheKey, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = await factory();
|
||||
_memoryCache.Set(cacheKey, result, absoluteExpiration);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
_memoryCache.Remove(key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public T GetOrAdd<T>(string cacheKey, Func<T> factory, DateTime absoluteExpiration)
|
||||
{
|
||||
// locks get and set internally
|
||||
if (_memoryCache.TryGetValue<T>(cacheKey, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
lock (TypeLock<T>.Lock)
|
||||
{
|
||||
if (_memoryCache.TryGetValue(cacheKey, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
result = factory();
|
||||
_memoryCache.Set(cacheKey, result, absoluteExpiration);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class TypeLock<T>
|
||||
{
|
||||
public static object Lock { get; } = new object();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
12
src/Ombi.Helpers/ICacheService.cs
Normal file
12
src/Ombi.Helpers/ICacheService.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ombi.Helpers
|
||||
{
|
||||
public interface ICacheService
|
||||
{
|
||||
Task<T> GetOrAdd<T>(string cacheKey, Func<Task<T>> factory, DateTime absoluteExpiration = default(DateTime));
|
||||
T GetOrAdd<T>(string cacheKey, Func<T> factory, DateTime absoluteExpiration);
|
||||
void Remove(string key);
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2017 Jamie Rees
|
||||
// File: MemoryCacheHelper.cs
|
||||
// Created By: Jamie Rees
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining
|
||||
// a copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to
|
||||
// permit persons to whom the Software is furnished to do so, subject to
|
||||
// the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be
|
||||
// included in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Ombi.Helpers
|
||||
{
|
||||
public static class MemoryCacheHelper
|
||||
{
|
||||
public static IMemoryCache TryAdd(this IMemoryCache cache, object cacheObject, TimeSpan slidingExpiration)
|
||||
{
|
||||
object cachedObject;
|
||||
if (!cache.TryGetValue(CacheKeys.Update, out cachedObject))
|
||||
{
|
||||
// Key not in cache, so get data.
|
||||
|
||||
// Set cache options.
|
||||
var cacheEntryOptions = new MemoryCacheEntryOptions()
|
||||
.SetSlidingExpiration(slidingExpiration);
|
||||
|
||||
// Save data in cache.
|
||||
cache.Set(CacheKeys.Update, cacheObject, cacheEntryOptions);
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
<PackageReference Include="Nito.AsyncEx" Version="5.0.0-pre-05" />
|
||||
<PackageReference Include="System.Security.Claims" Version="4.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -90,6 +90,9 @@ namespace Ombi.Notifications.Agents
|
|||
return;
|
||||
}
|
||||
|
||||
// Issues should be sent to admin
|
||||
message.To = settings.AdminEmail;
|
||||
|
||||
await Send(message, settings);
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ namespace Ombi.Notifications.Interfaces
|
|||
|
||||
// Is this a test?
|
||||
// The request id for tests is -1
|
||||
// Also issues are 0 since there might not be a request associated
|
||||
if (model.RequestId > 0)
|
||||
{
|
||||
await LoadRequest(model.RequestId, model.RequestType);
|
||||
|
@ -157,11 +158,11 @@ namespace Ombi.Notifications.Interfaces
|
|||
var curlys = new NotificationMessageCurlys();
|
||||
if (model.RequestType == RequestType.Movie)
|
||||
{
|
||||
curlys.Setup(MovieRequest, Customization);
|
||||
curlys.Setup(model, MovieRequest, Customization);
|
||||
}
|
||||
else
|
||||
{
|
||||
curlys.Setup(TvRequest, Customization);
|
||||
curlys.Setup(model, TvRequest, Customization);
|
||||
}
|
||||
var parsed = resolver.ParseMessage(template, curlys);
|
||||
|
||||
|
|
|
@ -12,5 +12,7 @@ namespace Ombi.Notifications.Models
|
|||
public NotificationType NotificationType { get; set; }
|
||||
public RequestType RequestType { get; set; }
|
||||
public string Recipient { get; set; }
|
||||
public string AdditionalInformation { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Settings.Settings.Models;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
|
@ -9,7 +10,7 @@ namespace Ombi.Notifications
|
|||
public class NotificationMessageCurlys
|
||||
{
|
||||
|
||||
public void Setup(FullBaseRequest req, CustomizationSettings s)
|
||||
public void Setup(NotificationOptions opts, FullBaseRequest req, CustomizationSettings s)
|
||||
{
|
||||
ApplicationUrl = s.ApplicationUrl;
|
||||
ApplicationName = string.IsNullOrEmpty(s.ApplicationName) ? "Ombi" : s.ApplicationName;
|
||||
|
@ -23,9 +24,10 @@ namespace Ombi.Notifications
|
|||
Year = req.ReleaseDate.Year.ToString();
|
||||
PosterImage = req.RequestType == RequestType.Movie ?
|
||||
$"https://image.tmdb.org/t/p/w300{req.PosterPath}" : req.PosterPath;
|
||||
AdditionalInformation = opts.AdditionalInformation;
|
||||
}
|
||||
|
||||
public void Setup(ChildRequests req, CustomizationSettings s)
|
||||
public void Setup(NotificationOptions opts, ChildRequests req, CustomizationSettings s)
|
||||
{
|
||||
ApplicationUrl = s.ApplicationUrl;
|
||||
ApplicationName = string.IsNullOrEmpty(s.ApplicationName) ? "Ombi" : s.ApplicationName;
|
||||
|
@ -39,6 +41,7 @@ namespace Ombi.Notifications
|
|||
Year = req.ParentRequest.ReleaseDate.Year.ToString();
|
||||
PosterImage = req.RequestType == RequestType.Movie ?
|
||||
$"https://image.tmdb.org/t/p/w300{req.ParentRequest.PosterPath}" : req.ParentRequest.PosterPath;
|
||||
AdditionalInformation = opts.AdditionalInformation;
|
||||
// DO Episode and Season Lists
|
||||
}
|
||||
|
||||
|
@ -54,7 +57,7 @@ namespace Ombi.Notifications
|
|||
public string Title { get; set; }
|
||||
public string RequestedDate { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Issue { get; set; }
|
||||
public string AdditionalInformation { get; set; }
|
||||
public string Overview { get; set; }
|
||||
public string Year { get; set; }
|
||||
public string EpisodesList { get; set; }
|
||||
|
@ -75,7 +78,7 @@ namespace Ombi.Notifications
|
|||
{nameof(Title), Title },
|
||||
{nameof(RequestedDate), RequestedDate },
|
||||
{nameof(Type), Type },
|
||||
{nameof(Issue), Issue },
|
||||
{nameof(AdditionalInformation), AdditionalInformation },
|
||||
{nameof(LongDate),LongDate},
|
||||
{nameof(ShortDate),ShortDate},
|
||||
{nameof(LongTime),LongTime},
|
||||
|
|
|
@ -67,7 +67,7 @@ namespace Ombi.Notifications
|
|||
/// <param name="model">The model.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
public async Task Publish(NotificationOptions model, Ombi.Settings.Settings.Models.Settings settings)
|
||||
public async Task Publish(NotificationOptions model, Settings.Settings.Models.Settings settings)
|
||||
{
|
||||
var notificationTasks = NotificationAgents.Select(notification => NotifyAsync(notification, model, settings));
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace Ombi.Schedule.Jobs.Radarr
|
|||
await SemaphoreSlim.WaitAsync();
|
||||
try
|
||||
{
|
||||
var settings = RadarrSettings.GetSettings();
|
||||
var settings = await RadarrSettings.GetSettingsAsync();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging;
|
|||
using Ombi.Api.Sonarr;
|
||||
using Ombi.Api.Sonarr.Models;
|
||||
using Ombi.Core.Settings;
|
||||
using Ombi.Core.Settings.Models.External;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Settings.Settings.Models.External;
|
||||
using Ombi.Store.Context;
|
||||
|
@ -60,6 +59,8 @@ namespace Ombi.Schedule.Jobs.Sonarr
|
|||
_log.LogDebug("Syncing series: {0}", s.title);
|
||||
var episodes = await _api.GetEpisodes(s.id, settings.ApiKey, settings.FullUri);
|
||||
var monitoredEpisodes = episodes.Where(x => x.monitored || x.hasFile);
|
||||
|
||||
// Add to DB
|
||||
_log.LogDebug("We have the episodes, adding to db transaction");
|
||||
await _ctx.SonarrEpisodeCache.AddRangeAsync(monitoredEpisodes.Select(episode => new SonarrEpisodeCache
|
||||
{
|
||||
|
@ -82,58 +83,5 @@ namespace Ombi.Schedule.Jobs.Sonarr
|
|||
SemaphoreSlim.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//public void Queued()
|
||||
//{
|
||||
// var settings = SonarrSettings.GetSettings();
|
||||
// if (settings.Enabled)
|
||||
// {
|
||||
// Job.SetRunning(true, JobNames.SonarrCacher);
|
||||
// try
|
||||
// {
|
||||
// var series = SonarrApi.GetSeries(settings.ApiKey, settings.FullUri);
|
||||
// if (series != null)
|
||||
// {
|
||||
// Cache.Set(CacheKeys.SonarrQueued, series, CacheKeys.TimeFrameMinutes.SchedulerCaching);
|
||||
// }
|
||||
// }
|
||||
// catch (System.Exception ex)
|
||||
// {
|
||||
// Log.Error(ex, "Failed caching queued items from Sonarr");
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// Job.Record(JobNames.SonarrCacher);
|
||||
// Job.SetRunning(false, JobNames.SonarrCacher);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//// we do not want to set here...
|
||||
//public IEnumerable<SonarrCachedResult> QueuedIds()
|
||||
//{
|
||||
// var result = new List<SonarrCachedResult>();
|
||||
|
||||
// var series = Cache.Get<List<Series>>(CacheKeys.SonarrQueued);
|
||||
// if (series != null)
|
||||
// {
|
||||
// foreach (var s in series)
|
||||
// {
|
||||
// var cached = new SonarrCachedResult { TvdbId = s.tvdbId };
|
||||
// foreach (var season in s.seasons)
|
||||
// {
|
||||
// cached.Seasons.Add(new SonarrSeasons
|
||||
// {
|
||||
// SeasonNumber = season.seasonNumber,
|
||||
// Monitored = season.monitored
|
||||
// });
|
||||
// }
|
||||
|
||||
// result.Add(cached);
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
//}
|
||||
}
|
||||
}
|
8
src/Ombi.Settings/Settings/Models/IssueSettings.cs
Normal file
8
src/Ombi.Settings/Settings/Models/IssueSettings.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Ombi.Settings.Settings.Models
|
||||
{
|
||||
public class IssueSettings : Settings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public bool EnableInProgress { get; set; }
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ using Ombi.Core.Settings;
|
|||
using Ombi.Helpers;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Repository;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Ombi.Settings.Settings
|
||||
{
|
||||
|
@ -13,7 +12,7 @@ namespace Ombi.Settings.Settings
|
|||
where T : Models.Settings, new()
|
||||
{
|
||||
|
||||
public SettingsService(ISettingsRepository repo, IMemoryCache cache)
|
||||
public SettingsService(ISettingsRepository repo, ICacheService cache)
|
||||
{
|
||||
Repo = repo;
|
||||
EntityName = typeof(T).Name;
|
||||
|
@ -23,13 +22,12 @@ namespace Ombi.Settings.Settings
|
|||
private ISettingsRepository Repo { get; }
|
||||
private string EntityName { get; }
|
||||
private string CacheName => $"Settings{EntityName}";
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ICacheService _cache;
|
||||
|
||||
public T GetSettings()
|
||||
{
|
||||
return _cache.GetOrCreate(CacheName, entry =>
|
||||
return _cache.GetOrAdd(CacheName, () =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2);
|
||||
var result = Repo.Get(EntityName);
|
||||
if (result == null)
|
||||
{
|
||||
|
@ -43,14 +41,13 @@ namespace Ombi.Settings.Settings
|
|||
var model = obj;
|
||||
|
||||
return model;
|
||||
});
|
||||
}, DateTime.Now.AddHours(2));
|
||||
}
|
||||
|
||||
public async Task<T> GetSettingsAsync()
|
||||
{
|
||||
return await _cache.GetOrCreateAsync(CacheName, async entry =>
|
||||
return await _cache.GetOrAdd(CacheName, async () =>
|
||||
{
|
||||
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(2);
|
||||
var result = await Repo.GetAsync(EntityName);
|
||||
if (result == null)
|
||||
{
|
||||
|
@ -64,7 +61,7 @@ namespace Ombi.Settings.Settings
|
|||
var model = obj;
|
||||
|
||||
return model;
|
||||
});
|
||||
}, DateTime.Now.AddHours(2));
|
||||
}
|
||||
|
||||
public bool SaveSettings(T model)
|
||||
|
|
|
@ -30,8 +30,8 @@ namespace Ombi.Store.Context
|
|||
DbSet<MovieRequests> MovieRequests { get; set; }
|
||||
DbSet<TvRequests> TvRequests { get; set; }
|
||||
DbSet<ChildRequests> ChildRequests { get; set; }
|
||||
DbSet<MovieIssues> MovieIssues { get; set; }
|
||||
DbSet<TvIssues> TvIssues { get; set; }
|
||||
DbSet<Issues> Issues { get; set; }
|
||||
DbSet<IssueCategory> IssueCategories { get; set; }
|
||||
DbSet<Tokens> Tokens { get; set; }
|
||||
DbSet<SonarrCache> SonarrCache { get; set; }
|
||||
DbSet<SonarrEpisodeCache> SonarrEpisodeCache { get; set; }
|
||||
|
|
|
@ -33,10 +33,13 @@ namespace Ombi.Store.Context
|
|||
public DbSet<MovieRequests> MovieRequests { get; set; }
|
||||
public DbSet<TvRequests> TvRequests { get; set; }
|
||||
public DbSet<ChildRequests> ChildRequests { get; set; }
|
||||
public DbSet<MovieIssues> MovieIssues { get; set; }
|
||||
public DbSet<TvIssues> TvIssues { get; set; }
|
||||
|
||||
public DbSet<Issues> Issues { get; set; }
|
||||
public DbSet<IssueCategory> IssueCategories { get; set; }
|
||||
public DbSet<IssueComments> IssueComments { get; set; }
|
||||
public DbSet<RequestLog> RequestLogs { get; set; }
|
||||
|
||||
|
||||
public DbSet<Audit> Audit { get; set; }
|
||||
public DbSet<Tokens> Tokens { get; set; }
|
||||
public DbSet<SonarrCache> SonarrCache { get; set; }
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Ombi.Store.Entities.Requests
|
|||
|
||||
|
||||
[ForeignKey(nameof(IssueId))]
|
||||
public List<TvIssues> Issues { get; set; }
|
||||
public List<Issues> Issues { get; set; }
|
||||
|
||||
public List<SeasonRequests> SeasonRequests { get; set; }
|
||||
}
|
||||
|
|
10
src/Ombi.Store/Entities/Requests/IssueCategory.cs
Normal file
10
src/Ombi.Store/Entities/Requests/IssueCategory.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities.Requests
|
||||
{
|
||||
[Table("IssueCategory")]
|
||||
public class IssueCategory : Entity
|
||||
{
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
18
src/Ombi.Store/Entities/Requests/IssueComments.cs
Normal file
18
src/Ombi.Store/Entities/Requests/IssueComments.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities.Requests
|
||||
{
|
||||
public class IssueComments : Entity
|
||||
{
|
||||
public string UserId { get; set; }
|
||||
public string Comment { get; set; }
|
||||
public int? IssuesId { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[ForeignKey(nameof(IssuesId))]
|
||||
public Issues Issues{ get; set; }
|
||||
[ForeignKey(nameof(UserId))]
|
||||
public OmbiUser User { get; set; }
|
||||
}
|
||||
}
|
33
src/Ombi.Store/Entities/Requests/Issues.cs
Normal file
33
src/Ombi.Store/Entities/Requests/Issues.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities.Requests
|
||||
{
|
||||
[Table("Issues")]
|
||||
public class Issues : Entity
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public RequestType RequestType { get; set; }
|
||||
public string ProviderId { get; set; }
|
||||
public int? RequestId { get; set; }
|
||||
public string Subject { get; set; }
|
||||
public string Description { get; set; }
|
||||
public int IssueCategoryId { get; set; }
|
||||
[ForeignKey(nameof(IssueCategoryId))]
|
||||
public IssueCategory IssueCategory { get; set; }
|
||||
public IssueStatus Status { get; set; }
|
||||
public DateTime? ResovledDate { get; set; }
|
||||
[ForeignKey(nameof(UserReported))]
|
||||
public string UserReportedId { get; set; }
|
||||
public OmbiUser UserReported { get; set; }
|
||||
public List<IssueComments> Comments { get; set; }
|
||||
}
|
||||
|
||||
public enum IssueStatus
|
||||
{
|
||||
Pending = 0,
|
||||
InProgress = 1,
|
||||
Resolved = 2,
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Ombi.Store.Entities.Requests
|
||||
{
|
||||
public class IssuesBase : Entity
|
||||
{
|
||||
public string Subect { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities.Requests
|
||||
{
|
||||
[Table("MovieIssues")]
|
||||
public class MovieIssues : IssuesBase
|
||||
{
|
||||
public int MovieId { get; set; }
|
||||
[ForeignKey(nameof(MovieId))]
|
||||
public MovieRequests Movie { get; set; }
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ namespace Ombi.Store.Entities.Requests
|
|||
public int TheMovieDbId { get; set; }
|
||||
public int? IssueId { get; set; }
|
||||
[ForeignKey(nameof(IssueId))]
|
||||
public List<MovieIssues> Issues { get; set; }
|
||||
public List<Issues> Issues { get; set; }
|
||||
|
||||
public int RootPathOverride { get; set; }
|
||||
public int QualityOverride { get; set; }
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Ombi.Store.Entities.Requests
|
||||
{
|
||||
[Table("TvIssues")]
|
||||
public class TvIssues : IssuesBase
|
||||
{
|
||||
public int TvId { get; set; }
|
||||
[ForeignKey(nameof(TvId))]
|
||||
public ChildRequests Child { get; set; }
|
||||
}
|
||||
}
|
|
@ -447,7 +447,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int>("MovieId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
@ -513,7 +513,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.Property<int>("TvId");
|
||||
|
||||
|
|
|
@ -504,7 +504,7 @@ namespace Ombi.Store.Migrations
|
|||
Description = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IssueId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
MovieId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Subect = table.Column<string>(type: "TEXT", nullable: true)
|
||||
Subject = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -551,7 +551,7 @@ namespace Ombi.Store.Migrations
|
|||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Description = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IssueId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
Subect = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Subject = table.Column<string>(type: "TEXT", nullable: true),
|
||||
TvId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
|
|
|
@ -447,7 +447,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int>("MovieId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
@ -515,7 +515,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.Property<int>("TvId");
|
||||
|
||||
|
|
|
@ -449,7 +449,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int>("MovieId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
@ -517,7 +517,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.Property<int>("TvId");
|
||||
|
||||
|
|
|
@ -449,7 +449,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int>("MovieId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
@ -517,7 +517,7 @@ namespace Ombi.Store.Migrations
|
|||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.Property<int>("TvId");
|
||||
|
||||
|
|
840
src/Ombi.Store/Migrations/20171213154624_Issues.Designer.cs
generated
Normal file
840
src/Ombi.Store/Migrations/20171213154624_Issues.Designer.cs
generated
Normal file
|
@ -0,0 +1,840 @@
|
|||
// <auto-generated />
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.EntityFrameworkCore.Storage.Internal;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using System;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
[DbContext(typeof(OmbiContext))]
|
||||
[Migration("20171213154624_Issues")]
|
||||
partial class Issues
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasName("RoleNameIndex");
|
||||
|
||||
b.ToTable("AspNetRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ClaimType");
|
||||
|
||||
b.Property<string>("ClaimValue");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("ProviderKey");
|
||||
|
||||
b.Property<string>("ProviderDisplayName");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("RoleId");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.Property<string>("LoginProvider");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.ApplicationConfiguration", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ApplicationConfiguration");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Audit", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AuditArea");
|
||||
|
||||
b.Property<int>("AuditType");
|
||||
|
||||
b.Property<DateTime>("DateTime");
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<string>("User");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Audit");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.CouchPotatoCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("CouchPotatoCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("EmbyId")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("EmbyContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("EmbyId");
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<string>("ParentId");
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
|
||||
b.ToTable("EmbyEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.GlobalSettings", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Content");
|
||||
|
||||
b.Property<string>("SettingsName");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("GlobalSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.NotificationTemplates", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("Agent");
|
||||
|
||||
b.Property<bool>("Enabled");
|
||||
|
||||
b.Property<string>("Message");
|
||||
|
||||
b.Property<int>("NotificationType");
|
||||
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("NotificationTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.OmbiUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("AccessFailedCount");
|
||||
|
||||
b.Property<string>("Alias");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken();
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<bool>("EmailConfirmed");
|
||||
|
||||
b.Property<string>("EmbyConnectUserId");
|
||||
|
||||
b.Property<DateTime?>("LastLoggedIn");
|
||||
|
||||
b.Property<bool>("LockoutEnabled");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<string>("PasswordHash");
|
||||
|
||||
b.Property<string>("PhoneNumber");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed");
|
||||
|
||||
b.Property<string>("ProviderUserId");
|
||||
|
||||
b.Property<string>("SecurityStamp");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256);
|
||||
|
||||
b.Property<int>("UserType");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasName("UserNameIndex");
|
||||
|
||||
b.ToTable("AspNetUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<int>("GrandparentKey");
|
||||
|
||||
b.Property<int>("Key");
|
||||
|
||||
b.Property<int>("ParentKey");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("GrandparentKey");
|
||||
|
||||
b.ToTable("PlexEpisode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("ParentKey");
|
||||
|
||||
b.Property<int>("PlexContentId");
|
||||
|
||||
b.Property<int?>("PlexServerContentId");
|
||||
|
||||
b.Property<int>("SeasonKey");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("PlexServerContentId");
|
||||
|
||||
b.ToTable("PlexSeasonsContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexServerContent", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AddedAt");
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<int>("Key");
|
||||
|
||||
b.Property<string>("Quality");
|
||||
|
||||
b.Property<string>("ReleaseYear");
|
||||
|
||||
b.Property<string>("TheMovieDbId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("TvDbId");
|
||||
|
||||
b.Property<int>("Type");
|
||||
|
||||
b.Property<string>("Url");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("PlexServerContent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.RadarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("RadarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<bool?>("Denied");
|
||||
|
||||
b.Property<string>("DeniedReason");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<int>("ParentRequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ParentRequestId");
|
||||
|
||||
b.HasIndex("RequestedUserId");
|
||||
|
||||
b.ToTable("ChildRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("IssueCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Comment");
|
||||
|
||||
b.Property<DateTime>("Date");
|
||||
|
||||
b.Property<int?>("IssuesId");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssuesId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("IssueComments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<int>("IssueCategoryId");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<int?>("RequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime?>("ResovledDate");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueCategoryId");
|
||||
|
||||
b.HasIndex("IssueId");
|
||||
|
||||
b.ToTable("Issues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<string>("Background");
|
||||
|
||||
b.Property<bool?>("Denied");
|
||||
|
||||
b.Property<string>("DeniedReason");
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Overview");
|
||||
|
||||
b.Property<string>("PosterPath");
|
||||
|
||||
b.Property<int>("QualityOverride");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime>("RequestedDate");
|
||||
|
||||
b.Property<string>("RequestedUserId");
|
||||
|
||||
b.Property<int>("RootPathOverride");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<int>("TheMovieDbId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RequestedUserId");
|
||||
|
||||
b.ToTable("MovieRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("ImdbId");
|
||||
|
||||
b.Property<string>("Overview");
|
||||
|
||||
b.Property<string>("PosterPath");
|
||||
|
||||
b.Property<DateTime>("ReleaseDate");
|
||||
|
||||
b.Property<int?>("RootFolder");
|
||||
|
||||
b.Property<string>("Status");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("TvRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SickRageEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SickRageEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.SonarrEpisodeCache", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.Property<int>("TvDbId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("SonarrEpisodeCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Token");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("Tokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTime>("AirDate");
|
||||
|
||||
b.Property<bool>("Approved");
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<int>("EpisodeNumber");
|
||||
|
||||
b.Property<bool>("Requested");
|
||||
|
||||
b.Property<int>("SeasonId");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("Url");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SeasonId");
|
||||
|
||||
b.ToTable("EpisodeRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("ChildRequestId");
|
||||
|
||||
b.Property<int>("SeasonNumber");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChildRequestId");
|
||||
|
||||
b.ToTable("SeasonRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole")
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.EmbyEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.EmbyContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("ParentId")
|
||||
.HasPrincipalKey("EmbyId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexEpisode", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent", "Series")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("GrandparentKey")
|
||||
.HasPrincipalKey("Key")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.PlexSeasonsContent", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.PlexServerContent")
|
||||
.WithMany("Seasons")
|
||||
.HasForeignKey("PlexServerContentId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.ChildRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.TvRequests", "ParentRequest")
|
||||
.WithMany("ChildRequests")
|
||||
.HasForeignKey("ParentRequestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.Issues", "Issues")
|
||||
.WithMany("Comments")
|
||||
.HasForeignKey("IssuesId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.IssueCategory", "IssueCategory")
|
||||
.WithMany()
|
||||
.HasForeignKey("IssueCategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
|
||||
.WithMany("Issues")
|
||||
.HasForeignKey("IssueId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.Requests.MovieRequests")
|
||||
.WithMany("Issues")
|
||||
.HasForeignKey("IssueId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.EpisodeRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Repository.Requests.SeasonRequests", "Season")
|
||||
.WithMany("Episodes")
|
||||
.HasForeignKey("SeasonId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Repository.Requests.SeasonRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "ChildRequest")
|
||||
.WithMany("SeasonRequests")
|
||||
.HasForeignKey("ChildRequestId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
138
src/Ombi.Store/Migrations/20171213154624_Issues.cs
Normal file
138
src/Ombi.Store/Migrations/20171213154624_Issues.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
{
|
||||
public partial class Issues : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "IssueCategory",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Value = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_IssueCategory", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Issues",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Description = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IssueCategoryId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
IssueId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
ProviderId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
RequestId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
RequestType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ResovledDate = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Subject = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Title = table.Column<string>(type: "TEXT", nullable: true),
|
||||
UserReportedId = table.Column<string>(type: "TEXT", nullable: true),
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Issues", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Issues_IssueCategory_IssueCategoryId",
|
||||
column: x => x.IssueCategoryId,
|
||||
principalTable: "IssueCategory",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Issues_ChildRequests_IssueId",
|
||||
column: x => x.IssueId,
|
||||
principalTable: "ChildRequests",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Issues_MovieRequests_IssueId",
|
||||
column: x => x.IssueId,
|
||||
principalTable: "MovieRequests",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Issues_AspNetUsers_UserReportedId",
|
||||
column: x => x.UserReportedId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "IssueComments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Comment = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Date = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
IssuesId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
UserId = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_IssueComments", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_IssueComments_Issues_IssuesId",
|
||||
column: x => x.IssuesId,
|
||||
principalTable: "Issues",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_IssueComments_AspNetUsers_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "AspNetUsers",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IssueComments_IssuesId",
|
||||
table: "IssueComments",
|
||||
column: "IssuesId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IssueComments_UserId",
|
||||
table: "IssueComments",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Issues_IssueCategoryId",
|
||||
table: "Issues",
|
||||
column: "IssueCategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Issues_IssueId",
|
||||
table: "Issues",
|
||||
column: "IssueId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Issues_UserReportedId",
|
||||
table: "Issues",
|
||||
column: "UserReportedId");
|
||||
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "IssueComments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Issues");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "IssueCategory");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ using Microsoft.EntityFrameworkCore.Storage.Internal;
|
|||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using System;
|
||||
|
||||
namespace Ombi.Store.Migrations
|
||||
|
@ -441,26 +442,76 @@ namespace Ombi.Store.Migrations
|
|||
b.ToTable("ChildRequests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b =>
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueCategory", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Value");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("IssueCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.IssueComments", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Comment");
|
||||
|
||||
b.Property<DateTime>("Date");
|
||||
|
||||
b.Property<int?>("IssuesId");
|
||||
|
||||
b.Property<string>("UserId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssuesId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("IssueComments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.Issues", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<int>("IssueCategoryId");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<int>("MovieId");
|
||||
b.Property<string>("ProviderId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
b.Property<int?>("RequestId");
|
||||
|
||||
b.Property<int>("RequestType");
|
||||
|
||||
b.Property<DateTime?>("ResovledDate");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("Subject");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("UserReportedId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueCategoryId");
|
||||
|
||||
b.HasIndex("IssueId");
|
||||
|
||||
b.HasIndex("MovieId");
|
||||
b.HasIndex("UserReportedId");
|
||||
|
||||
b.ToTable("MovieIssues");
|
||||
b.ToTable("Issues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
|
@ -533,28 +584,6 @@ namespace Ombi.Store.Migrations
|
|||
b.ToTable("RequestLog");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Description");
|
||||
|
||||
b.Property<int?>("IssueId");
|
||||
|
||||
b.Property<string>("Subect");
|
||||
|
||||
b.Property<int>("TvId");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IssueId");
|
||||
|
||||
b.HasIndex("TvId");
|
||||
|
||||
b.ToTable("TvIssues");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvRequests", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
|
@ -778,23 +807,34 @@ namespace Ombi.Store.Migrations
|
|||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieIssues", b =>
|
||||
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.Requests.MovieRequests", "Movie")
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "UserReported")
|
||||
.WithMany()
|
||||
.HasForeignKey("MovieId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("RequestedUserId");
|
||||
.HasForeignKey("UserReportedId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.RequestLog", b =>
|
||||
|
@ -804,16 +844,12 @@ namespace Ombi.Store.Migrations
|
|||
.HasForeignKey("UserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.TvIssues", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests")
|
||||
.WithMany("Issues")
|
||||
.HasForeignKey("IssueId");
|
||||
|
||||
b.HasOne("Ombi.Store.Entities.Requests.ChildRequests", "Child")
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Requests.MovieRequests", b =>
|
||||
{
|
||||
b.HasOne("Ombi.Store.Entities.OmbiUser", "RequestedUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("TvId")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
.HasForeignKey("RequestedUserId");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Ombi.Store.Entities.Tokens", b =>
|
||||
|
|
|
@ -7,7 +7,6 @@ namespace Ombi.Store.Repository
|
|||
{
|
||||
public interface IPlexContentRepository : IRepository<PlexServerContent>
|
||||
{
|
||||
Task<PlexServerContent> Add(PlexServerContent content);
|
||||
Task<bool> ContentExists(string providerId);
|
||||
Task<PlexServerContent> Get(string providerId);
|
||||
Task<PlexServerContent> GetByKey(int key);
|
||||
|
|
|
@ -14,10 +14,10 @@ namespace Ombi.Store.Repository
|
|||
IQueryable<T> GetAll();
|
||||
Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate);
|
||||
Task AddRange(IEnumerable<T> content);
|
||||
Task<T> Add(T content);
|
||||
Task DeleteRange(IEnumerable<T> req);
|
||||
Task Delete(T request);
|
||||
Task<int> SaveChangesAsync();
|
||||
Task<T> Add(T content);
|
||||
|
||||
IIncludableQueryable<TEntity, TProperty> Include<TEntity, TProperty>(
|
||||
IQueryable<TEntity> source, Expression<Func<TEntity, TProperty>> navigationPropertyPath)
|
||||
|
|
|
@ -6,7 +6,6 @@ namespace Ombi.Store.Repository.Requests
|
|||
{
|
||||
public interface IMovieRequestRepository : IRepository<MovieRequests>
|
||||
{
|
||||
Task<MovieRequests> Add(MovieRequests request);
|
||||
Task<MovieRequests> GetRequestAsync(int theMovieDbId);
|
||||
MovieRequests GetRequest(int theMovieDbId);
|
||||
Task Update(MovieRequests request);
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Store.Context;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
|
@ -11,14 +12,14 @@ namespace Ombi.Store.Repository
|
|||
{
|
||||
public class SettingsJsonRepository : ISettingsRepository
|
||||
{
|
||||
public SettingsJsonRepository(IOmbiContext ctx, IMemoryCache mem)
|
||||
public SettingsJsonRepository(IOmbiContext ctx, ICacheService mem)
|
||||
{
|
||||
Db = ctx;
|
||||
_cache = mem;
|
||||
}
|
||||
|
||||
private IOmbiContext Db { get; }
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ICacheService _cache;
|
||||
|
||||
public GlobalSettings Insert(GlobalSettings entity)
|
||||
{
|
||||
|
|
|
@ -34,6 +34,12 @@
|
|||
<i class="fa fa-plus"></i> {{ 'NavigationBar.Requests' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul *ngIf="issuesEnabled" class="nav navbar-nav">
|
||||
<li id="Requests" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/issues']">
|
||||
<i class="fa fa-exclamation-circle"></i> {{ 'NavigationBar.Issues' | translate }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul *ngIf="hasRole('Admin') || hasRole('PowerUser')" class="nav navbar-nav">
|
||||
<li id="UserManagement" [routerLinkActive]="['active']">
|
||||
<a [routerLink]="['/usermanagement']">
|
||||
|
|
|
@ -16,6 +16,7 @@ import { ICustomizationSettings } from "./interfaces";
|
|||
export class AppComponent implements OnInit {
|
||||
|
||||
public customizationSettings: ICustomizationSettings;
|
||||
public issuesEnabled = false;
|
||||
public user: ILocalUser;
|
||||
public showNav: boolean;
|
||||
public updateAvailable: boolean;
|
||||
|
@ -42,6 +43,7 @@ export class AppComponent implements OnInit {
|
|||
this.user = this.authService.claims();
|
||||
|
||||
this.settingsService.getCustomization().subscribe(x => this.customizationSettings = x);
|
||||
this.settingsService.issueEnabled().subscribe(x => this.issuesEnabled = x);
|
||||
|
||||
this.router.events.subscribe((event: NavigationStart) => {
|
||||
this.currentUrl = event.url;
|
||||
|
|
|
@ -10,14 +10,12 @@ import {RouterModule, Routes} from "@angular/router";
|
|||
|
||||
import { JwtModule } from "@auth0/angular-jwt";
|
||||
|
||||
// Third Party
|
||||
//import { DragulaModule, DragulaService } from 'ng2-dragula/ng2-dragula';
|
||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
|
||||
import { TranslateHttpLoader } from "@ngx-translate/http-loader";
|
||||
import { CookieService } from "ng2-cookies";
|
||||
import { GrowlModule } from "primeng/components/growl/growl";
|
||||
import { ButtonModule, CaptchaModule,ConfirmationService, ConfirmDialogModule, DataTableModule,DialogModule, SharedModule, TooltipModule } from "primeng/primeng";
|
||||
import { ButtonModule, CaptchaModule, ConfirmationService, ConfirmDialogModule, DataTableModule,DialogModule, SharedModule, SidebarModule, TooltipModule } from "primeng/primeng";
|
||||
|
||||
// Components
|
||||
import { AppComponent } from "./app.component";
|
||||
|
@ -36,28 +34,24 @@ import { IdentityService } from "./services";
|
|||
import { ImageService } from "./services";
|
||||
import { LandingPageService } from "./services";
|
||||
import { NotificationService } from "./services";
|
||||
import { RequestService } from "./services";
|
||||
import { SettingsService } from "./services";
|
||||
import { StatusService } from "./services";
|
||||
|
||||
// Modules
|
||||
import { RequestsModule } from "./requests/requests.module";
|
||||
import { SearchModule } from "./search/search.module";
|
||||
import { SettingsModule } from "./settings/settings.module";
|
||||
import { UserManagementModule } from "./usermanagement/usermanagement.module";
|
||||
import { WizardModule } from "./wizard/wizard.module";
|
||||
import { IssuesService, JobService, StatusService } from "./services";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "*", component: PageNotFoundComponent },
|
||||
{ path: "", redirectTo: "/search", pathMatch: "full" },
|
||||
|
||||
//{ path: 'requests-grid', component: RequestGridComponent },
|
||||
{ path: "login", component: LoginComponent },
|
||||
{ path: "login/:landing", component: LoginComponent },
|
||||
{ path: "reset", component: ResetPasswordComponent },
|
||||
{ path: "token", component: TokenResetPasswordComponent },
|
||||
{ path: "landingpage", component: LandingPageComponent },
|
||||
{ path: "auth/cookie", component: CookieComponent },
|
||||
{ loadChildren: "./issues/issues.module#IssuesModule", path: "issues" },
|
||||
{ loadChildren: "./settings/settings.module#SettingsModule", path: "Settings" },
|
||||
{ loadChildren: "./wizard/wizard.module#WizardModule", path: "Wizard" },
|
||||
{ loadChildren: "./usermanagement/usermanagement.module#UserManagementModule", path: "usermanagement" },
|
||||
{ loadChildren: "./requests/requests.module#RequestsModule", path: "requests" },
|
||||
{ loadChildren: "./search/search.module#SearchModule", path: "search" },
|
||||
];
|
||||
|
||||
// AoT requires an exported function for factories
|
||||
|
@ -79,11 +73,8 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
|
|||
GrowlModule,
|
||||
ButtonModule,
|
||||
FormsModule,
|
||||
SettingsModule,
|
||||
DataTableModule,
|
||||
SharedModule,
|
||||
WizardModule,
|
||||
SearchModule,
|
||||
DialogModule,
|
||||
MatButtonModule,
|
||||
NgbModule.forRoot(),
|
||||
|
@ -91,8 +82,6 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
|
|||
MatInputModule,
|
||||
MatTabsModule,
|
||||
ReactiveFormsModule,
|
||||
UserManagementModule,
|
||||
RequestsModule,
|
||||
CaptchaModule,
|
||||
TooltipModule,
|
||||
ConfirmDialogModule,
|
||||
|
@ -115,6 +104,7 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
|
|||
deps: [HttpClient, PlatformLocation],
|
||||
},
|
||||
}),
|
||||
SidebarModule,
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
|
@ -126,7 +116,6 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
|
|||
CookieComponent,
|
||||
],
|
||||
providers: [
|
||||
RequestService,
|
||||
NotificationService,
|
||||
AuthService,
|
||||
AuthGuard,
|
||||
|
@ -137,6 +126,8 @@ export function HttpLoaderFactory(http: HttpClient, platformLocation: PlatformLo
|
|||
ConfirmationService,
|
||||
ImageService,
|
||||
CookieService,
|
||||
JobService,
|
||||
IssuesService,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
|
63
src/Ombi/ClientApp/app/interfaces/IIssues.ts
Normal file
63
src/Ombi/ClientApp/app/interfaces/IIssues.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { IIssueCategory, IUser, RequestType } from "./";
|
||||
|
||||
export interface IIssues {
|
||||
id?: number;
|
||||
title: string;
|
||||
requestType: RequestType;
|
||||
providerId: string;
|
||||
subject: string;
|
||||
description: string;
|
||||
issueCategory: IIssueCategory;
|
||||
issueCategoryId: number;
|
||||
status: IssueStatus;
|
||||
resolvedDate?: Date;
|
||||
comments: IIssueComments[];
|
||||
requestId: number | undefined;
|
||||
userReported: IUser | undefined;
|
||||
}
|
||||
|
||||
export enum IssueStatus {
|
||||
Pending = 0,
|
||||
InProgress = 1,
|
||||
Resolved = 2,
|
||||
}
|
||||
|
||||
export interface IIssueCount {
|
||||
pending: number;
|
||||
inProgress: number;
|
||||
resolved: number;
|
||||
}
|
||||
|
||||
export interface IPagenator {
|
||||
first: number;
|
||||
rows: number;
|
||||
page: number;
|
||||
pageCount: number;
|
||||
}
|
||||
|
||||
export interface IIssueComments {
|
||||
userId: string;
|
||||
comment: string;
|
||||
movieIssueId: number | undefined;
|
||||
tvIssueId: number | undefined;
|
||||
date: Date;
|
||||
user: IUser;
|
||||
issues: IIssues | undefined;
|
||||
}
|
||||
|
||||
export interface IIssuesChat {
|
||||
comment: string;
|
||||
date: Date;
|
||||
username: string;
|
||||
adminComment: boolean;
|
||||
}
|
||||
|
||||
export interface INewIssueComments {
|
||||
comment: string;
|
||||
issueId: number;
|
||||
}
|
||||
|
||||
export interface IUpdateStatus {
|
||||
issueId: number;
|
||||
status: IssueStatus;
|
||||
}
|
|
@ -23,58 +23,11 @@ export interface IMediaBase {
|
|||
released: boolean;
|
||||
}
|
||||
|
||||
//export interface IMovieRequestModel extends IMediaBase { }
|
||||
|
||||
export interface ITvRequestModel extends IMediaBase {
|
||||
imdbId: string;
|
||||
tvDbId: string;
|
||||
childRequests: IChildTvRequest[];
|
||||
rootFolderSelected: number;
|
||||
firstAired: string;
|
||||
}
|
||||
|
||||
export interface IRequestCountModel {
|
||||
pending: number;
|
||||
approved: number;
|
||||
available: number;
|
||||
}
|
||||
|
||||
export interface IChildTvRequest extends IMediaBase {
|
||||
requestAll: boolean;
|
||||
seasonRequests: ISeasonRequests[];
|
||||
}
|
||||
|
||||
export interface ISeasonRequests {
|
||||
seasonNumber: number;
|
||||
episodes: IEpisodesRequested[];
|
||||
}
|
||||
|
||||
export interface IEpisodesRequested {
|
||||
episodeNumber: number;
|
||||
title: string;
|
||||
airDate: Date;
|
||||
url: string;
|
||||
requested: boolean;
|
||||
status: string;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
export enum RequestType {
|
||||
movie = 1,
|
||||
tvShow = 2,
|
||||
}
|
||||
|
||||
export interface IRequestsPageScroll {
|
||||
count: number;
|
||||
position: number;
|
||||
}
|
||||
|
||||
export interface IRequestGrid<T> {
|
||||
available: T[];
|
||||
new: T[];
|
||||
approved: T[];
|
||||
}
|
||||
|
||||
// NEW WORLD
|
||||
|
||||
export interface IMovieRequests extends IFullBaseRequest {
|
||||
|
@ -117,6 +70,7 @@ export interface IBaseRequest {
|
|||
requestType: RequestType;
|
||||
requestedUser: IUser;
|
||||
canApprove: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface ITvRequests {
|
||||
|
|
|
@ -122,6 +122,11 @@ export interface IJobSettings {
|
|||
sickRageSync: string;
|
||||
}
|
||||
|
||||
export interface IIssueSettings extends ISettings {
|
||||
enabled: boolean;
|
||||
enableInProgress: boolean;
|
||||
}
|
||||
|
||||
export interface IAuthenticationSettings extends ISettings {
|
||||
allowNoPassword: boolean;
|
||||
// Password
|
||||
|
@ -179,3 +184,7 @@ export interface IDogNzbSettings extends ISettings {
|
|||
movies: boolean;
|
||||
tvShows: boolean;
|
||||
}
|
||||
|
||||
export interface IIssueCategory extends ISettings {
|
||||
value: string;
|
||||
}
|
||||
|
|
|
@ -12,3 +12,4 @@ export * from "./ISearchTvResult";
|
|||
export * from "./ISettings";
|
||||
export * from "./ISonarr";
|
||||
export * from "./IUser";
|
||||
export * from "./IIssues";
|
||||
|
|
76
src/Ombi/ClientApp/app/issues/issueDetails.component.html
Normal file
76
src/Ombi/ClientApp/app/issues/issueDetails.component.html
Normal file
|
@ -0,0 +1,76 @@
|
|||
<div *ngIf="issue">
|
||||
<h1>{{issue.title}} </h1>
|
||||
<div class="col-md-6">
|
||||
<span class="label label-info">{{IssueStatus[issue.status]}}</span>
|
||||
<span class="label label-success">{{issue.issueCategory.value}}</span>
|
||||
|
||||
<h3 *ngIf="issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.alias}}</h3>
|
||||
<h3 *ngIf="!issue.userReported?.alias">{{'Issues.ReportedBy' | translate}}: {{issue.userReported.userName}}</h3>
|
||||
<h3 *ngIf="issue.subject">{{'Issues.Subject' | translate}}: {{issue.subject}}</h3>
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<label for="description" class="control-label" [translate]="'Issues.Description'"></label>
|
||||
<div>
|
||||
<textarea class="form-control-custom form-control" disabled="disabled" [(ngModel)]="issue.description" rows="5" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row chat-window col-xs-7 col-md-5" id="chat_window_1" style="margin-left:10px;">
|
||||
<div class="col-xs-12 col-md-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading top-bar">
|
||||
<div class="col-md-8 col-xs-8">
|
||||
<h3 class="panel-title">
|
||||
<span class="glyphicon glyphicon-comment"></span> {{'Issues.Comments' | translate}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="comments" class="panel-body msg_container_base">
|
||||
<div *ngIf="comments.length <= 0" class="row msg_container base_receive">
|
||||
<div class="col-md-10 col-xs-10">
|
||||
<div class="messages msg_sent">
|
||||
<p [translate]="'Issues.NoComments'"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngFor="let comment of comments" class="row msg_container" [ngClass]="{'base_sent': comment.adminComment, 'base_receive': !comment.adminComment}">
|
||||
<div class="col-md-10 col-xs-10">
|
||||
<div class="messages msg_sent">
|
||||
<p>{{comment.comment}}</p>
|
||||
<time>{{comment.username}} • {{comment.date | date:'short'}}</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<div class="input-group">
|
||||
<input id="btn-input" type="text" class="form-control input-sm chat_input" [(ngModel)]="newComment.comment" [attr.placeholder]="'Issues.WriteMessagePlaceholder' | translate"
|
||||
/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary btn-sm" id="btn-chat" (click)="addComment()" [translate]="'Issues.SendMessageButton'"></button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<div *ngIf="isAdmin && settings">
|
||||
<div *ngIf="issue.status === IssueStatus.Pending && settings.enableInProgress">
|
||||
<button class="btn btn-primary btn-sm" (click)="inProgress()" [translate]="'Issues.MarkInProgress'"></button>
|
||||
</div>
|
||||
<div *ngIf="issue.status === IssueStatus.Pending && !settings.enableInProgress || issue.status == IssueStatus.InProgress">
|
||||
<button class="btn btn-primary btn-sm" (click)="resolve()" [translate]="'Issues.MarkResolved'"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
137
src/Ombi/ClientApp/app/issues/issueDetails.component.scss
Normal file
137
src/Ombi/ClientApp/app/issues/issueDetails.component.scss
Normal file
|
@ -0,0 +1,137 @@
|
|||
$color:#424242;
|
||||
|
||||
body{
|
||||
height:400px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
}
|
||||
.col-md-2, .col-md-10{
|
||||
padding:0;
|
||||
}
|
||||
.panel{
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.chat-window{
|
||||
float:right;
|
||||
margin-left:10px;
|
||||
}
|
||||
.chat-window > div > .panel{
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
.icon_minim{
|
||||
padding:2px 10px;
|
||||
}
|
||||
.msg_container_base{
|
||||
background: #e5e5e5;
|
||||
margin: 0;
|
||||
padding: 0 10px 10px;
|
||||
max-height:300px;
|
||||
overflow-x:hidden;
|
||||
}
|
||||
.top-bar {
|
||||
background: $color;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.msg_receive{
|
||||
padding-left:0;
|
||||
margin-left:0;
|
||||
}
|
||||
.msg_sent{
|
||||
padding-bottom:20px !important;
|
||||
margin-right:0;
|
||||
}
|
||||
.messages {
|
||||
background: white;
|
||||
padding: 10px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
max-width:100%;
|
||||
color:black;
|
||||
}
|
||||
.messages > p {
|
||||
font-size: 13px;
|
||||
margin: 0 0 0.2rem 0;
|
||||
}
|
||||
.messages > time {
|
||||
font-size: 11px;
|
||||
color: #ccc;
|
||||
}
|
||||
.msg_container {
|
||||
padding: 10px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.avatar {
|
||||
position: relative;
|
||||
}
|
||||
.base_receive > .avatar:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 5px solid #FFF;
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
border-bottom-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.base_sent {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
.base_sent > .avatar:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border: 5px solid white;
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.msg_sent > time{
|
||||
float: right;
|
||||
}
|
||||
|
||||
.msg_container_base::-webkit-scrollbar-track
|
||||
{
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.msg_container_base::-webkit-scrollbar
|
||||
{
|
||||
width: 12px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
.msg_container_base::-webkit-scrollbar-thumb
|
||||
{
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
.btn-group.dropup{
|
||||
position:fixed;
|
||||
left:0px;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
padding: 10px 15px;
|
||||
background-color: $color;
|
||||
border-top: 1px solid transparent;
|
||||
border-bottom-right-radius: -1;
|
||||
border-bottom-left-radius: -1;
|
||||
}
|
88
src/Ombi/ClientApp/app/issues/issueDetails.component.ts
Normal file
88
src/Ombi/ClientApp/app/issues/issueDetails.component.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { IssuesService, NotificationService, SettingsService } from "../services";
|
||||
|
||||
import { IIssues, IIssuesChat, IIssueSettings, INewIssueComments, IssueStatus } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
templateUrl: "issueDetails.component.html",
|
||||
styleUrls: ["./issueDetails.component.scss"],
|
||||
})
|
||||
export class IssueDetailsComponent implements OnInit {
|
||||
|
||||
public issue: IIssues;
|
||||
public comments: IIssuesChat[];
|
||||
public newComment: INewIssueComments = {
|
||||
comment: "",
|
||||
issueId: 0,
|
||||
};
|
||||
|
||||
public IssueStatus = IssueStatus;
|
||||
public isAdmin: boolean;
|
||||
public settings: IIssueSettings;
|
||||
|
||||
private issueId: number;
|
||||
|
||||
constructor(private issueService: IssuesService,
|
||||
private route: ActivatedRoute,
|
||||
private authService: AuthService,
|
||||
private settingsService: SettingsService,
|
||||
private notificationService: NotificationService) {
|
||||
this.route.params
|
||||
.subscribe((params: any) => {
|
||||
this.issueId = parseInt(params.id);
|
||||
});
|
||||
|
||||
this.isAdmin = this.authService.hasRole("Admin") || this.authService.hasRole("PowerUser");
|
||||
this.settingsService.getIssueSettings().subscribe(x => this.settings = x);
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.issueService.getIssue(this.issueId).subscribe(x => {
|
||||
this.issue = {
|
||||
comments: x.comments,
|
||||
id: x.id,
|
||||
issueCategory: x.issueCategory,
|
||||
issueCategoryId: x.issueCategoryId,
|
||||
subject: x.subject,
|
||||
description: x.description,
|
||||
status:x.status,
|
||||
resolvedDate:x.resolvedDate,
|
||||
title: x.title,
|
||||
requestType: x.requestType,
|
||||
requestId: x.requestId,
|
||||
providerId: x.providerId,
|
||||
userReported: x.userReported,
|
||||
};
|
||||
});
|
||||
|
||||
this.loadComments();
|
||||
}
|
||||
|
||||
public addComment() {
|
||||
this.newComment.issueId = this.issueId;
|
||||
this.issueService.addComment(this.newComment).subscribe(x => {
|
||||
this.loadComments();
|
||||
});
|
||||
}
|
||||
|
||||
public inProgress() {
|
||||
this.issueService.updateStatus({issueId: this.issueId, status: IssueStatus.InProgress}).subscribe(x => {
|
||||
this.notificationService.success("Marked issue as In Progress");
|
||||
this.issue.status = IssueStatus.InProgress;
|
||||
});
|
||||
}
|
||||
|
||||
public resolve() {
|
||||
this.issueService.updateStatus({issueId: this.issueId, status: IssueStatus.Resolved}).subscribe(x => {
|
||||
this.notificationService.success("Marked issue as Resolved");
|
||||
this.issue.status = IssueStatus.Resolved;
|
||||
});
|
||||
}
|
||||
|
||||
private loadComments() {
|
||||
this.issueService.getComments(this.issueId).subscribe(x => this.comments = x);
|
||||
}
|
||||
}
|
28
src/Ombi/ClientApp/app/issues/issues.component.html
Normal file
28
src/Ombi/ClientApp/app/issues/issues.component.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<h1 id="issuesTitle" [translate]="'Issues.Title'"></h1>
|
||||
|
||||
<ngb-tabset *ngIf="count">
|
||||
<ngb-tab *ngIf="count.pending > 0">
|
||||
<ng-template ngbTabTitle>{{'Issues.PendingTitle' | translate}} <span class="badge">{{count.pending}}</span></ng-template>
|
||||
<ng-template ngbTabContent>
|
||||
<div *ngIf="pendingIssues">
|
||||
<issues-table [issues]="pendingIssues" (changePage)="changePagePending($event)" [totalRecords]="count.pending"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
<ngb-tab *ngIf="count.inProgress > 0">
|
||||
<ng-template ngbTabTitle>{{'Issues.InProgressTitle' | translate}} <span class="badge">{{count.inProgress}}</span></ng-template>
|
||||
<ng-template ngbTabContent>
|
||||
<div *ngIf="inProgressIssues">
|
||||
<issues-table [issues]="inProgressIssues" (changePage)="changePageInProg($event)" [totalRecords]="count.inProgress"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
<ngb-tab *ngIf="count.resolved > 0">
|
||||
<ng-template ngbTabTitle>{{'Issues.ResolvedTitle' | translate}} <span class="badge">{{count.resolved}}</span></ng-template>
|
||||
<ng-template ngbTabContent>
|
||||
<div *ngIf="resolvedIssues">
|
||||
<issues-table [issues]="resolvedIssues" (changePage)="changePageResolved($event)" [totalRecords]="count.resolved"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ngb-tab>
|
||||
</ngb-tabset>
|
65
src/Ombi/ClientApp/app/issues/issues.component.ts
Normal file
65
src/Ombi/ClientApp/app/issues/issues.component.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { IssuesService } from "../services";
|
||||
|
||||
import { IIssueCount, IIssues, IPagenator, IssueStatus } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
templateUrl: "issues.component.html",
|
||||
})
|
||||
export class IssuesComponent implements OnInit {
|
||||
|
||||
public pendingIssues: IIssues[];
|
||||
public inProgressIssues: IIssues[];
|
||||
public resolvedIssues: IIssues[];
|
||||
|
||||
public count: IIssueCount;
|
||||
|
||||
private takeAmount = 10;
|
||||
private pendingSkip = 0;
|
||||
private inProgressSkip = 0;
|
||||
private resolvedSkip = 0;
|
||||
|
||||
constructor(private issueService: IssuesService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.getPending();
|
||||
this.getInProg();
|
||||
this.getResolved();
|
||||
this.issueService.getIssuesCount().subscribe(x => this.count = x);
|
||||
}
|
||||
|
||||
public changePagePending(event: IPagenator) {
|
||||
this.pendingSkip = event.first;
|
||||
this.getPending();
|
||||
}
|
||||
|
||||
public changePageInProg(event: IPagenator) {
|
||||
this.inProgressSkip = event.first;
|
||||
this.getInProg();
|
||||
}
|
||||
|
||||
public changePageResolved(event: IPagenator) {
|
||||
this.resolvedSkip = event.first;
|
||||
this.getResolved();
|
||||
}
|
||||
|
||||
private getPending() {
|
||||
this.issueService.getIssuesPage(this.takeAmount, this.pendingSkip, IssueStatus.Pending).subscribe(x => {
|
||||
this.pendingIssues = x;
|
||||
});
|
||||
}
|
||||
|
||||
private getInProg() {
|
||||
this.issueService.getIssuesPage(this.takeAmount, this.inProgressSkip, IssueStatus.InProgress).subscribe(x => {
|
||||
this.inProgressIssues = x;
|
||||
});
|
||||
}
|
||||
|
||||
private getResolved() {
|
||||
this.issueService.getIssuesPage(this.takeAmount, this.resolvedSkip, IssueStatus.Resolved).subscribe(x => {
|
||||
this.resolvedIssues = x;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
49
src/Ombi/ClientApp/app/issues/issues.module.ts
Normal file
49
src/Ombi/ClientApp/app/issues/issues.module.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
|
||||
import { OrderModule } from "ngx-order-pipe";
|
||||
import { PaginatorModule, SharedModule, TabViewModule } from "primeng/primeng";
|
||||
|
||||
import { IdentityService } from "../services";
|
||||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
import { SharedModule as OmbiShared } from "../shared/shared.module";
|
||||
|
||||
import { IssueDetailsComponent } from "./issueDetails.component";
|
||||
import { IssuesComponent } from "./issues.component";
|
||||
import { IssuesTableComponent } from "./issuestable.component";
|
||||
|
||||
import { PipeModule } from "../pipes/pipe.module";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", component: IssuesComponent, canActivate: [AuthGuard] },
|
||||
{ path: ":id", component: IssueDetailsComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
NgbModule.forRoot(),
|
||||
SharedModule,
|
||||
OrderModule,
|
||||
PipeModule,
|
||||
OmbiShared,
|
||||
PaginatorModule,
|
||||
TabViewModule,
|
||||
],
|
||||
declarations: [
|
||||
IssuesComponent,
|
||||
IssueDetailsComponent,
|
||||
IssuesTableComponent,
|
||||
],
|
||||
exports: [
|
||||
RouterModule,
|
||||
],
|
||||
providers: [
|
||||
IdentityService,
|
||||
],
|
||||
|
||||
})
|
||||
export class IssuesModule { }
|
55
src/Ombi/ClientApp/app/issues/issuestable.component.html
Normal file
55
src/Ombi/ClientApp/app/issues/issuestable.component.html
Normal file
|
@ -0,0 +1,55 @@
|
|||
<table class="table table-striped table-hover table-responsive table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th (click)="setOrder('title')">
|
||||
<a [translate]="'Issues.ColumnTitle'"></a>
|
||||
<span *ngIf="order === 'title'">
|
||||
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="setOrder('issueCategory.value')">
|
||||
<a [translate]="'Issues.Category'"></a>
|
||||
<span *ngIf="order === 'issueCategory.value'">
|
||||
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="setOrder('issue.status')">
|
||||
<a [translate]="'Issues.Status'"></a>
|
||||
<span *ngIf="order === 'issue.status'">
|
||||
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th (click)="setOrder('issue.reportedUser')">
|
||||
<a [translate]="'Issues.ReportedBy'"></a>
|
||||
<span *ngIf="order === 'issue.reportedUser'">
|
||||
<span [hidden]="reverse"><i class="fa fa-arrow-down" aria-hidden="true"></i></span><span [hidden]="!reverse"><i class="fa fa-arrow-up" aria-hidden="true"></i></span>
|
||||
</span>
|
||||
</th>
|
||||
<th>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let issue of issues | orderBy: order : reverse : 'case-insensitive'">
|
||||
<td>
|
||||
{{issue.title}}
|
||||
</td>
|
||||
<td>
|
||||
{{issue.issueCategory.value}}
|
||||
</td>
|
||||
<td>
|
||||
{{IssueStatus[issue.status] | humanize}}
|
||||
</td>
|
||||
<td *ngIf="issue.userReported?.alias">
|
||||
{{issue.userReported.alias}}
|
||||
</td>
|
||||
<td *ngIf="!issue.userReported?.alias">
|
||||
{{issue.userReported.userName}}
|
||||
</td>
|
||||
<td>
|
||||
<a [routerLink]="['/issues/' + issue.id]" class="btn btn-sm btn-info-outline" [translate]="'Issues.Details'"></a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p-paginator [rows]="rowCount" [totalRecords]="totalRecords" (onPageChange)="paginate($event)"></p-paginator>
|
40
src/Ombi/ClientApp/app/issues/issuestable.component.ts
Normal file
40
src/Ombi/ClientApp/app/issues/issuestable.component.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { IIssues, IPagenator, IssueStatus } from "./../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "issues-table",
|
||||
templateUrl: "issuestable.component.html",
|
||||
})
|
||||
export class IssuesTableComponent {
|
||||
|
||||
@Input() public issues: IIssues[];
|
||||
@Input() public totalRecords: number;
|
||||
|
||||
@Output() public changePage = new EventEmitter<IPagenator>();
|
||||
|
||||
public IssueStatus = IssueStatus;
|
||||
|
||||
public order: string = "id";
|
||||
public reverse = false;
|
||||
|
||||
public rowCount = 10;
|
||||
|
||||
public setOrder(value: string) {
|
||||
if (this.order === value) {
|
||||
this.reverse = !this.reverse;
|
||||
}
|
||||
|
||||
this.order = value;
|
||||
}
|
||||
|
||||
public paginate(event: IPagenator) {
|
||||
//event.first = Index of the first record (current index)
|
||||
//event.rows = Number of rows to display in new page
|
||||
//event.page = Index of the new page
|
||||
//event.pageCount = Total number of pages
|
||||
|
||||
this.changePage.emit(event);
|
||||
}
|
||||
|
||||
}
|
|
@ -107,26 +107,18 @@
|
|||
<button *ngIf="!request.available" (click)="changeAvailability(request, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--<div class="dropdown">
|
||||
<button id="{{request.requestId}}" class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<div class="dropdown" *ngIf="issueCategories && issuesEnabled">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li><a issue-select="0">@UI.Issues_WrongAudio</a></li>
|
||||
<li><a issue-select="1">@UI.Issues_NoSubs</a></li>
|
||||
<li><a issue-select="2">@UI.Issues_WrongContent</a></li>
|
||||
<li><a issue-select="3">@UI.Issues_Playback</a></li>
|
||||
<li><a issue-select="4" data-toggle="modal" data-target="#myModal">@UI.Issues_Other</a></li>
|
||||
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, request)">{{cat.value}}</a></li>
|
||||
</ul>
|
||||
</div>-->
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -138,3 +130,7 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequest?.title"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" [providerId]=""></issue-report>
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import "rxjs/add/operator/debounceTime";
|
||||
import "rxjs/add/operator/distinctUntilChanged";
|
||||
|
@ -8,7 +8,7 @@ import { Subject } from "rxjs/Subject";
|
|||
import { AuthService } from "../auth/auth.service";
|
||||
import { NotificationService, RadarrService, RequestService } from "../services";
|
||||
|
||||
import { IMovieRequests, IRadarrProfile, IRadarrRootFolder } from "../interfaces";
|
||||
import { IIssueCategory, IMovieRequests, IRadarrProfile, IRadarrRootFolder } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "movie-requests",
|
||||
|
@ -25,6 +25,13 @@ export class MovieRequestsComponent implements OnInit {
|
|||
public radarrProfiles: IRadarrProfile[];
|
||||
public radarrRootFolders: IRadarrRootFolder[];
|
||||
|
||||
@Input() public issueCategories: IIssueCategory[];
|
||||
@Input() public issuesEnabled: boolean;
|
||||
public issuesBarVisible = false;
|
||||
public issueRequest: IMovieRequests;
|
||||
public issueProviderId: string;
|
||||
public issueCategorySelected: IIssueCategory;
|
||||
|
||||
private currentlyLoaded: number;
|
||||
private amountToLoad: number;
|
||||
|
||||
|
@ -121,6 +128,17 @@ export class MovieRequestsComponent implements OnInit {
|
|||
this.updateRequest(searchResult);
|
||||
}
|
||||
|
||||
public reportIssue(catId: IIssueCategory, req: IMovieRequests) {
|
||||
this.issueRequest = req;
|
||||
this.issueCategorySelected = catId;
|
||||
this.issuesBarVisible = true;
|
||||
this.issueProviderId = req.theMovieDbId.toString();
|
||||
}
|
||||
|
||||
public ignore(event: any): void {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
private loadRequests(amountToLoad: number, currentlyLoaded: number) {
|
||||
this.requestService.getMovieRequests(amountToLoad, currentlyLoaded + 1)
|
||||
.subscribe(x => {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<h1 id="searchTitle" [translate]="'Requests.Title'"></h1>
|
||||
<h4 [translate]="'Requests.Paragraph'"></h4>
|
||||
|
||||
|
||||
|
||||
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active">
|
||||
<a id="movieTabButton" aria-controls="home" role="tab" data-toggle="tab" (click)="selectMovieTab()"><i class="fa fa-film"></i> {{ 'Requests.MoviesTab' | translate }}</a>
|
||||
|
@ -16,10 +14,10 @@
|
|||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div [hidden]="!showMovie">
|
||||
<movie-requests></movie-requests>
|
||||
<movie-requests [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></movie-requests>
|
||||
</div>
|
||||
<div [hidden]="!showTv">
|
||||
<tv-requests></tv-requests>
|
||||
<tv-requests [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></tv-requests>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
import { Component } from "@angular/core";
|
||||
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { IIssueCategory } from "./../interfaces";
|
||||
import { IssuesService, SettingsService } from "./../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./request.component.html",
|
||||
})
|
||||
export class RequestComponent {
|
||||
export class RequestComponent implements OnInit {
|
||||
|
||||
public showMovie = true;
|
||||
public showTv = false;
|
||||
|
||||
public issueCategories: IIssueCategory[];
|
||||
public issuesEnabled = false;
|
||||
|
||||
constructor(private issuesService: IssuesService,
|
||||
private settingsService: SettingsService) {
|
||||
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.issuesService.getCategories().subscribe(x => this.issueCategories = x);
|
||||
this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled);
|
||||
}
|
||||
|
||||
public selectMovieTab() {
|
||||
this.showMovie = true;
|
||||
this.showTv = false;
|
||||
|
|
|
@ -12,20 +12,18 @@ import { RequestComponent } from "./request.component";
|
|||
import { TvRequestChildrenComponent } from "./tvrequest-children.component";
|
||||
import { TvRequestsComponent } from "./tvrequests.component";
|
||||
|
||||
import { TreeTableModule } from "primeng/primeng";
|
||||
import { SidebarModule, TreeTableModule } from "primeng/primeng";
|
||||
|
||||
import { IdentityService } from "../services";
|
||||
import { RequestService } from "../services";
|
||||
import { IdentityService, RadarrService, RequestService } from "../services";
|
||||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "requests", component: RequestComponent, canActivate: [AuthGuard] },
|
||||
{ path: "requests/:id", component: TvRequestChildrenComponent, canActivate: [AuthGuard] },
|
||||
{ path: "", component: RequestComponent, canActivate: [AuthGuard] },
|
||||
{ path: ":id", component: TvRequestChildrenComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
|
@ -35,6 +33,7 @@ const routes: Routes = [
|
|||
DialogModule,
|
||||
TreeTableModule,
|
||||
SharedModule,
|
||||
SidebarModule,
|
||||
],
|
||||
declarations: [
|
||||
RequestComponent,
|
||||
|
@ -48,6 +47,7 @@ const routes: Routes = [
|
|||
providers: [
|
||||
IdentityService,
|
||||
RequestService,
|
||||
RadarrService,
|
||||
],
|
||||
|
||||
})
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
</div>
|
||||
|
||||
|
||||
<div class="col-md-1 col-md-push-9" *ngIf="isAdmin">
|
||||
|
||||
<div class="col-md-1 col-md-push-9">
|
||||
<div *ngIf="isAdmin">
|
||||
<button *ngIf="child.canApprove && !child.approved" (click)="approve(child)" class="btn btn-sm btn-success-outline" type="submit"><i class="fa fa-plus"></i> {{ 'Common.Approve' | translate }}</button>
|
||||
<button *ngIf="child.available" (click)="changeAvailability(child, false)" style="text-align: right" value="false" class="btn btn-sm btn-info-outline change"><i class="fa fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}</button>
|
||||
<button *ngIf="!child.available" (click)="changeAvailability(child, true)" style="text-align: right" value="true" class="btn btn-sm btn-success-outline change"><i class="fa fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}</button>
|
||||
|
@ -21,6 +21,16 @@
|
|||
<button *ngIf="!child.denied" type="button" (click)="deny(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> {{ 'Requests.Deny' | translate }}</button>
|
||||
|
||||
<button type="button" (click)="removeRequest(child)" class="btn btn-sm btn-danger-outline deny"><i class="fa fa-times"></i> {{ 'Requests.Remove' | translate }}</button>
|
||||
</div>
|
||||
<div class="dropdown" *ngIf="issueCategories && issuesEnabled">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, child)">{{cat.value}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -91,3 +101,8 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<issue-report [movie]="false" [visible]="issuesBarVisible" [title]="issueRequest?.title"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequest?.id" (visibleChange)="issuesBarVisible = $event;"></issue-report>
|
|
@ -1,5 +1,6 @@
|
|||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { IChildRequests } from "../interfaces";
|
||||
import { IChildRequests, IIssueCategory } from "../interfaces";
|
||||
|
||||
import { NotificationService, RequestService } from "../services";
|
||||
|
||||
@Component({
|
||||
|
@ -12,6 +13,13 @@ export class TvRequestChildrenComponent {
|
|||
|
||||
@Output() public requestDeleted = new EventEmitter<number>();
|
||||
|
||||
@Input() public issueCategories: IIssueCategory[];
|
||||
@Input() public issuesEnabled: boolean;
|
||||
@Input() public issueProviderId: string;
|
||||
public issuesBarVisible = false;
|
||||
public issueRequest: IChildRequests;
|
||||
public issueCategorySelected: IIssueCategory;
|
||||
|
||||
constructor(private requestService: RequestService,
|
||||
private notificationService: NotificationService) { }
|
||||
|
||||
|
@ -93,6 +101,12 @@ export class TvRequestChildrenComponent {
|
|||
});
|
||||
}
|
||||
|
||||
public reportIssue(catId: IIssueCategory, req: IChildRequests) {
|
||||
this.issueRequest = req;
|
||||
this.issueCategorySelected = catId;
|
||||
this.issuesBarVisible = true;
|
||||
}
|
||||
|
||||
private removeRequestFromUi(key: IChildRequests) {
|
||||
const index = this.childRequests.indexOf(key, 0);
|
||||
if (index > -1) {
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
</div>
|
||||
<!--This is the section that holds the child seasons if they want to specify specific episodes-->
|
||||
<div *ngIf="node.leaf">
|
||||
<tvrequests-children [childRequests]="node.data" [isAdmin] ="isAdmin" (requestDeleted)="childRequestDeleted($event)" ></tvrequests-children>
|
||||
<tvrequests-children [childRequests]="node.data" [isAdmin] ="isAdmin" (requestDeleted)="childRequestDeleted($event)" [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled" [issueProviderId]="node.data.tvDbId" ></tvrequests-children>
|
||||
</div>
|
||||
</ng-template>
|
||||
</p-column>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import "rxjs/add/operator/debounceTime";
|
||||
import "rxjs/add/operator/distinctUntilChanged";
|
||||
|
@ -14,7 +14,7 @@ import { AuthService } from "../auth/auth.service";
|
|||
import { RequestService } from "../services";
|
||||
|
||||
import { TreeNode } from "primeng/primeng";
|
||||
import { ITvRequests } from "../interfaces";
|
||||
import { IIssueCategory, ITvRequests } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "tv-requests",
|
||||
|
@ -30,6 +30,10 @@ export class TvRequestsComponent implements OnInit {
|
|||
public showChildDialogue = false; // This is for the child modal popup
|
||||
public selectedSeason: ITvRequests;
|
||||
|
||||
@Input() public issueCategories: IIssueCategory[];
|
||||
@Input() public issuesEnabled: boolean;
|
||||
public issueProviderId: string;
|
||||
|
||||
private currentlyLoaded: number;
|
||||
private amountToLoad: number;
|
||||
|
||||
|
|
|
@ -81,6 +81,15 @@
|
|||
<div *ngIf="result.available">
|
||||
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
|
||||
</div>
|
||||
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li *ngFor="let cat of issueCategories"><a [routerLink]="" (click)="reportIssue(cat, result)">{{cat.value}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -91,3 +100,7 @@
|
|||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<issue-report [movie]="true" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import "rxjs/add/operator/debounceTime";
|
||||
|
@ -7,10 +7,9 @@ import "rxjs/add/operator/map";
|
|||
import { Subject } from "rxjs/Subject";
|
||||
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { IIssueCategory, IRequestEngineResult, ISearchMovieResult } from "../interfaces";
|
||||
import { NotificationService, RequestService, SearchService } from "../services";
|
||||
|
||||
import { IRequestEngineResult, ISearchMovieResult } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "movie-search",
|
||||
templateUrl: "./moviesearch.component.html",
|
||||
|
@ -23,6 +22,14 @@ export class MovieSearchComponent implements OnInit {
|
|||
public result: IRequestEngineResult;
|
||||
public searchApplied = false;
|
||||
|
||||
@Input() public issueCategories: IIssueCategory[];
|
||||
@Input() public issuesEnabled: boolean;
|
||||
public issuesBarVisible = false;
|
||||
public issueRequestTitle: string;
|
||||
public issueRequestId: number;
|
||||
public issueProviderId: string;
|
||||
public issueCategorySelected: IIssueCategory;
|
||||
|
||||
constructor(private searchService: SearchService, private requestService: RequestService,
|
||||
private notificationService: NotificationService, private authService: AuthService,
|
||||
private readonly translate: TranslateService, private sanitizer: DomSanitizer) {
|
||||
|
@ -131,6 +138,14 @@ export class MovieSearchComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
public reportIssue(catId: IIssueCategory, req: ISearchMovieResult) {
|
||||
this.issueRequestId = req.id;
|
||||
this.issueRequestTitle = req.title;
|
||||
this.issueCategorySelected = catId;
|
||||
this.issuesBarVisible = true;
|
||||
this.issueProviderId = req.id.toString();
|
||||
}
|
||||
|
||||
private getExtraInfo() {
|
||||
|
||||
this.movieResults.forEach((val, index) => {
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
<div class="tab-content">
|
||||
|
||||
<div [hidden]="!showMovie">
|
||||
<movie-search></movie-search>
|
||||
<movie-search [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></movie-search>
|
||||
</div>
|
||||
|
||||
<div [hidden]="!showTv">
|
||||
<tv-search></tv-search>
|
||||
<tv-search [issueCategories]="issueCategories" [issuesEnabled]="issuesEnabled"></tv-search>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,14 +1,27 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { IIssueCategory } from "./../interfaces";
|
||||
import { IssuesService, SettingsService } from "./../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./search.component.html",
|
||||
})
|
||||
export class SearchComponent implements OnInit {
|
||||
public showTv: boolean;
|
||||
public showMovie: boolean;
|
||||
public issueCategories: IIssueCategory[];
|
||||
public issuesEnabled = false;
|
||||
|
||||
constructor(private issuesService: IssuesService,
|
||||
private settingsService: SettingsService) {
|
||||
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.showMovie = true;
|
||||
this.showTv = false;
|
||||
this.issuesService.getCategories().subscribe(x => this.issueCategories = x);
|
||||
this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled);
|
||||
}
|
||||
|
||||
public selectMovieTab() {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { SearchComponent } from "./search.component";
|
|||
import { SeriesInformationComponent } from "./seriesinformation.component";
|
||||
import { TvSearchComponent } from "./tvsearch.component";
|
||||
|
||||
import { TreeTableModule } from "primeng/primeng";
|
||||
import { SidebarModule, TreeTableModule } from "primeng/primeng";
|
||||
|
||||
import { RequestService } from "../services";
|
||||
import { SearchService } from "../services";
|
||||
|
@ -21,10 +21,9 @@ import { AuthGuard } from "../auth/auth.guard";
|
|||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "search", component: SearchComponent, canActivate: [AuthGuard] },
|
||||
{ path: "search/show/:id", component: SeriesInformationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "", component: SearchComponent, canActivate: [AuthGuard] },
|
||||
{ path: "show/:id", component: SeriesInformationComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -33,6 +32,7 @@ const routes: Routes = [
|
|||
NgbModule.forRoot(),
|
||||
TreeTableModule,
|
||||
SharedModule,
|
||||
SidebarModule,
|
||||
],
|
||||
declarations: [
|
||||
SearchComponent,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Component, Input, OnDestroy, OnInit} from "@angular/core";
|
||||
//import { ActivatedRoute } from '@angular/router';
|
||||
import { Subject } from "rxjs/Subject";
|
||||
|
||||
import "rxjs/add/operator/takeUntil";
|
||||
|
@ -37,7 +36,6 @@ export class SeriesInformationComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
public submitRequests() {
|
||||
|
||||
// Make sure something has been selected
|
||||
const selected = this.series.seasonRequests.some((season) => {
|
||||
return season.episodes.some((ep) => {
|
||||
|
|
|
@ -9,12 +9,21 @@
|
|||
<i class="fa fa-chevron-down"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a (click)="popularShows()">Popular Shows</a></li>
|
||||
<li><a (click)="trendingShows()">Trending Shows</a></li>
|
||||
<li><a (click)="mostWatchedShows()">Most Watched Shows</a></li>
|
||||
<li><a (click)="anticipatedShows()">Most Anticipated Shows</a></li>
|
||||
<li>
|
||||
<a (click)="popularShows()">Popular Shows</a>
|
||||
</li>
|
||||
<li>
|
||||
<a (click)="trendingShows()">Trending Shows</a>
|
||||
</li>
|
||||
<li>
|
||||
<a (click)="mostWatchedShows()">Most Watched Shows</a>
|
||||
</li>
|
||||
<li>
|
||||
<a (click)="anticipatedShows()">Most Anticipated Shows</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div><i id="tvSearchButton" class="fa fa-search"></i>
|
||||
</div>
|
||||
<i id="tvSearchButton" class="fa fa-search"></i>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
|
@ -24,13 +33,14 @@
|
|||
</div>
|
||||
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<!-- TV content -->
|
||||
<div id="tvList">
|
||||
|
||||
<div *ngIf="searchApplied && tvResults?.length <= 0" class='no-search-results'>
|
||||
<i class='fa fa-film no-search-results-icon'></i><div class='no-search-results-text'>Sorry, we didn't find any results!</div>
|
||||
<i class='fa fa-film no-search-results-icon'></i>
|
||||
<div class='no-search-results-text'>Sorry, we didn't find any results!</div>
|
||||
</div>
|
||||
<p-treeTable [value]="tvResults">
|
||||
<p-column>
|
||||
|
@ -65,11 +75,19 @@
|
|||
<ng-template [ngIf]="node.data.partlyAvailable"><span class="label label-warning" id="partiallyAvailableLabel">Partially Available</span></ng-template>
|
||||
|
||||
|
||||
<ng-template [ngIf]="node.data.available">
|
||||
<span class="label label-success">Available</span>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="node.data.partlyAvailable">
|
||||
<span class="label label-warning">Partially Available</span>
|
||||
</ng-template>
|
||||
|
||||
<a *ngIf="node.data.homepage" href="{{node.data.homepage}}" target="_blank"><span class="label label-info" id="homepageLabel">HomePage</span></a>
|
||||
|
||||
<a *ngIf="node.data.trailer" href="{{node.data.trailer}}" target="_blank"><span class="label label-info" id="trailerLabel">Trailer</span></a>
|
||||
|
||||
|
||||
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
|
@ -84,19 +102,42 @@
|
|||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li><a href="#" (click)="allSeasons(node.data, $event)">All Seasons</a></li>
|
||||
<li><a href="#" (click)="firstSeason(node.data, $event)">First Season</a></li>
|
||||
<li><a href="#" (click)="latestSeason(node.data, $event)">Latest Season</a></li>
|
||||
<li><a href="#" (click)="openClosestTab($event)">Select ...</a></li>
|
||||
<li>
|
||||
<a href="#" (click)="allSeasons(node.data, $event)">All Seasons</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" (click)="firstSeason(node.data, $event)">First Season</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" (click)="latestSeason(node.data, $event)">Latest Season</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" (click)="openClosestTab($event)">Select ...</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div *ngIf="node.data.fullyAvailable">
|
||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button>
|
||||
<button style="text-align: right" class="btn btn-success-outline disabled" disabled>
|
||||
<i class="fa fa-check"></i> Available</button>
|
||||
</div>
|
||||
<br />
|
||||
<div *ngIf="node.data.available">
|
||||
<a *ngIf="node.data.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a>
|
||||
<a *ngIf="node.data.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{node.data.plexUrl}}"
|
||||
target="_blank">
|
||||
<i class="fa fa-eye"></i> View On Plex</a>
|
||||
</div>
|
||||
<div class="dropdown" *ngIf="issueCategories && issuesEnabled">
|
||||
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="true">
|
||||
<i class="fa fa-plus"></i> Report Issue
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li *ngFor="let cat of issueCategories">
|
||||
<a [routerLink]="" (click)="reportIssue(cat, node.data)">{{cat.value}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div *ngIf="!node.data.available">
|
||||
<br/>
|
||||
|
@ -119,3 +160,7 @@
|
|||
</p-treeTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<issue-report [movie]="false" [visible]="issuesBarVisible" (visibleChange)="issuesBarVisible = $event;" [title]="issueRequestTitle"
|
||||
[issueCategory]="issueCategorySelected" [id]="issueRequestId" [providerId]="issueProviderId"></issue-report>
|
|
@ -1,6 +1,5 @@
|
|||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Component, Input, OnInit } from "@angular/core";
|
||||
import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject } from "rxjs/Subject";
|
||||
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
|
@ -8,14 +7,14 @@ import { ImageService, NotificationService, RequestService, SearchService} from
|
|||
|
||||
import { TreeNode } from "primeng/primeng";
|
||||
import { IRequestEngineResult } from "../interfaces";
|
||||
import { ISearchTvResult } from "../interfaces";
|
||||
import { IIssueCategory, ISearchTvResult } from "../interfaces";
|
||||
|
||||
@Component({
|
||||
selector: "tv-search",
|
||||
templateUrl: "./tvsearch.component.html",
|
||||
styleUrls: ["./../requests/tvrequests.component.scss"],
|
||||
})
|
||||
export class TvSearchComponent implements OnInit, OnDestroy {
|
||||
export class TvSearchComponent implements OnInit {
|
||||
|
||||
public searchText: string;
|
||||
public searchChanged = new Subject<string>();
|
||||
|
@ -23,16 +22,21 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
public result: IRequestEngineResult;
|
||||
public searchApplied = false;
|
||||
|
||||
private subscriptions = new Subject<void>();
|
||||
@Input() public issueCategories: IIssueCategory[];
|
||||
@Input() public issuesEnabled: boolean;
|
||||
public issuesBarVisible = false;
|
||||
public issueRequestTitle: string;
|
||||
public issueRequestId: number;
|
||||
public issueProviderId: string;
|
||||
public issueCategorySelected: IIssueCategory;
|
||||
|
||||
constructor(private searchService: SearchService, private requestService: RequestService,
|
||||
private notificationService: NotificationService, private route: Router, private authService: AuthService,
|
||||
private notificationService: NotificationService, private authService: AuthService,
|
||||
private imageService: ImageService, private sanitizer: DomSanitizer) {
|
||||
|
||||
this.searchChanged
|
||||
.debounceTime(600) // Wait Xms after the last event before emitting last event
|
||||
.distinctUntilChanged() // only emit if value is different from previous value
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.searchText = x as string;
|
||||
if (this.searchText === "") {
|
||||
|
@ -40,7 +44,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
this.searchService.searchTvTreeNode(this.searchText)
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.tvResults = x;
|
||||
this.searchApplied = true;
|
||||
|
@ -91,7 +94,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
public popularShows() {
|
||||
this.clearResults();
|
||||
this.searchService.popularTv()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.tvResults = x;
|
||||
this.getExtraInfo();
|
||||
|
@ -101,7 +103,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
public trendingShows() {
|
||||
this.clearResults();
|
||||
this.searchService.trendingTv()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.tvResults = x;
|
||||
this.getExtraInfo();
|
||||
|
@ -111,7 +112,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
public mostWatchedShows() {
|
||||
this.clearResults();
|
||||
this.searchService.mostWatchedTv()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.tvResults = x;
|
||||
this.getExtraInfo();
|
||||
|
@ -121,7 +121,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
public anticipatedShows() {
|
||||
this.clearResults();
|
||||
this.searchService.anticipatedTv()
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.tvResults = x;
|
||||
this.getExtraInfo();
|
||||
|
@ -138,7 +137,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
("linear-gradient(to bottom, rgba(0,0,0,0.6) 0%,rgba(0,0,0,0.6) 100%),url(" + x + ")");
|
||||
});
|
||||
this.searchService.getShowInformationTreeNode(val.data.id)
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
if (x.data) {
|
||||
this.updateItem(val, x);
|
||||
|
@ -158,7 +156,6 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
searchResult.approved = true;
|
||||
}
|
||||
this.requestService.requestTv(searchResult)
|
||||
.takeUntil(this.subscriptions)
|
||||
.subscribe(x => {
|
||||
this.result = x;
|
||||
if (this.result.result) {
|
||||
|
@ -192,13 +189,12 @@ export class TvSearchComponent implements OnInit, OnDestroy {
|
|||
this.request(searchResult);
|
||||
}
|
||||
|
||||
public selectSeason(searchResult: ISearchTvResult) {
|
||||
this.route.navigate(["/search/show", searchResult.id]);
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.subscriptions.next();
|
||||
this.subscriptions.complete();
|
||||
public reportIssue(catId: IIssueCategory, req: ISearchTvResult) {
|
||||
this.issueRequestId = req.id;
|
||||
this.issueRequestTitle = req.title;
|
||||
this.issueCategorySelected = catId;
|
||||
this.issuesBarVisible = true;
|
||||
this.issueProviderId = req.id.toString();
|
||||
}
|
||||
|
||||
private updateItem(key: TreeNode, updated: TreeNode) {
|
||||
|
|
|
@ -10,3 +10,4 @@ export * from "./service.helpers";
|
|||
export * from "./settings.service";
|
||||
export * from "./status.service";
|
||||
export * from "./job.service";
|
||||
export * from "./issues.service";
|
||||
|
|
59
src/Ombi/ClientApp/app/services/issues.service.ts
Normal file
59
src/Ombi/ClientApp/app/services/issues.service.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { PlatformLocation } from "@angular/common";
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Observable } from "rxjs/Rx";
|
||||
|
||||
import { IIssueCategory, IIssueComments,IIssueCount, IIssues, IIssuesChat, INewIssueComments, IssueStatus, IUpdateStatus } from "../interfaces";
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
||||
@Injectable()
|
||||
export class IssuesService extends ServiceHelpers {
|
||||
constructor(http: HttpClient, public platformLocation: PlatformLocation) {
|
||||
super(http, "/api/v1/Issues/", platformLocation);
|
||||
}
|
||||
|
||||
public getCategories(): Observable<IIssueCategory[]> {
|
||||
return this.http.get<IIssueCategory[]>(`${this.url}categories/`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public createCategory(cat: IIssueCategory): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}categories/`, JSON.stringify(cat), {headers: this.headers});
|
||||
}
|
||||
|
||||
public deleteCategory(cat: number): Observable<boolean> {
|
||||
return this.http.delete<boolean>(`${this.url}categories/${cat}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getIssues(): Observable<IIssues[]> {
|
||||
return this.http.get<IIssues[]>(this.url, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getIssuesPage(take: number, skip: number, status: IssueStatus): Observable<IIssues[]> {
|
||||
return this.http.get<IIssues[]>(`${this.url}${take}/${skip}/${status}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getIssuesCount(): Observable<IIssueCount> {
|
||||
return this.http.get<IIssueCount>(`${this.url}count`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public createIssue(issue: IIssues): Observable<number> {
|
||||
return this.http.post<number>(this.url, JSON.stringify(issue), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getIssue(id: number): Observable<IIssues> {
|
||||
return this.http.get<IIssues>(`${this.url}${id}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getComments(id: number): Observable<IIssuesChat[]> {
|
||||
return this.http.get<IIssuesChat[]>(`${this.url}${id}/comments`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public addComment(comment: INewIssueComments): Observable<IIssueComments> {
|
||||
return this.http.post<IIssueComments>(`${this.url}comments`, JSON.stringify(comment), {headers: this.headers});
|
||||
}
|
||||
|
||||
public updateStatus(model: IUpdateStatus): Observable<boolean> {
|
||||
return this.http.post<boolean>(`${this.url}status`, JSON.stringify(model), { headers: this.headers });
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import {
|
|||
IDogNzbSettings,
|
||||
IEmailNotificationSettings,
|
||||
IEmbySettings,
|
||||
IIssueSettings,
|
||||
IJobSettings,
|
||||
ILandingPageSettings,
|
||||
IMattermostNotifcationSettings,
|
||||
|
@ -233,4 +234,17 @@ export class SettingsService extends ServiceHelpers {
|
|||
return this.http
|
||||
.post<boolean>(`${this.url}/sickrage`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
|
||||
public getIssueSettings(): Observable<IIssueSettings> {
|
||||
return this.http.get<IIssueSettings>(`${this.url}/issues`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public issueEnabled(): Observable<boolean> {
|
||||
return this.http.get<boolean>(`${this.url}/issuesenabled`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public saveIssueSettings(settings: IIssueSettings): Observable<boolean> {
|
||||
return this.http
|
||||
.post<boolean>(`${this.url}/issues`, JSON.stringify(settings), {headers: this.headers});
|
||||
}
|
||||
}
|
||||
|
|
61
src/Ombi/ClientApp/app/settings/issues/issues.component.html
Normal file
61
src/Ombi/ClientApp/app/settings/issues/issues.component.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
<settings-menu></settings-menu>
|
||||
<wiki [url]="'https://github.com/tidusjar/Ombi/wiki/Issue-Settings'"></wiki>
|
||||
|
||||
<fieldset>
|
||||
<legend>Issues</legend>
|
||||
<form *ngIf="form" novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enable" formControlName="enabled" ng-checked="form.enabled">
|
||||
<label for="enable">Enable</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<input type="checkbox" id="enableInProgress" formControlName="enableInProgress" ng-checked="form.enableInProgress">
|
||||
<label for="enableInProgress">Enable In Progress State</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<button type="submit" [disabled]="form.invalid" class="btn btn-primary-outline ">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="col-md-6">
|
||||
<div *ngIf="categories">
|
||||
<div class="form-group row">
|
||||
<div class="col-md-12">
|
||||
<label for="categoryToAdd" class="control-label">Add Category</label>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<input type="text" [(ngModel)]="categoryToAdd.value" class="form-control form-control-custom " id="categoryToAdd" name="categoryToAdd"
|
||||
value="{{categoryToAdd.value}}">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-primary-outline" (click)="addCategory()">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div *ngFor="let cat of categories">
|
||||
<div class="col-md-9">
|
||||
{{cat.value}}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button class="btn btn-sm btn-danger-outline" (click)="deleteCategory(cat.id)">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
70
src/Ombi/ClientApp/app/settings/issues/issues.component.ts
Normal file
70
src/Ombi/ClientApp/app/settings/issues/issues.component.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, FormGroup } from "@angular/forms";
|
||||
|
||||
import { IIssueCategory } from "../../interfaces";
|
||||
import { IssuesService, NotificationService, SettingsService } from "../../services";
|
||||
|
||||
@Component({
|
||||
templateUrl: "./issues.component.html",
|
||||
})
|
||||
export class IssuesComponent implements OnInit {
|
||||
|
||||
public categories: IIssueCategory[];
|
||||
public categoryToAdd: IIssueCategory = {id: 0, value: ""};
|
||||
public form: FormGroup;
|
||||
|
||||
constructor(private issuesService: IssuesService,
|
||||
private settingsService: SettingsService,
|
||||
private readonly fb: FormBuilder,
|
||||
private notificationService: NotificationService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.settingsService.getIssueSettings().subscribe(x => {
|
||||
this.form = this.fb.group({
|
||||
enabled: [x.enabled],
|
||||
enableInProgress: [x.enableInProgress],
|
||||
});
|
||||
});
|
||||
this.getCategories();
|
||||
}
|
||||
|
||||
public addCategory(): void {
|
||||
this.issuesService.createCategory(this.categoryToAdd).subscribe(x => {
|
||||
if(x) {
|
||||
this.getCategories();
|
||||
this.categoryToAdd.value = "";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public deleteCategory(id: number) {
|
||||
this.issuesService.deleteCategory(id).subscribe(x => {
|
||||
if(x) {
|
||||
this.getCategories();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public onSubmit(form: FormGroup) {
|
||||
if (form.invalid) {
|
||||
this.notificationService.error("Please check your entered values");
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = form.value;
|
||||
|
||||
this.settingsService.saveIssueSettings(settings).subscribe(x => {
|
||||
if (x) {
|
||||
this.notificationService.success("Successfully saved the Issue settings");
|
||||
} else {
|
||||
this.notificationService.success("There was an error when saving the Issue settings");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private getCategories() {
|
||||
this.issuesService.getCategories().subscribe(x => {
|
||||
this.categories = x;
|
||||
});
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@
|
|||
<p class="form-group">Notice Message</p>
|
||||
<div class="form-group">
|
||||
<div>
|
||||
<textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. The server will be down for maintaince (HTML is allowed)" [(ngModel)]="settings.noticeText">{{noticeText}}</textarea>
|
||||
<textarea rows="4" type="text" class="form-control-custom form-control " id="NoticeMessage" name="NoticeMessage" placeholder="e.g. The server will be down for maintaince (HTML is allowed)" [(ngModel)]="settings.noticeText">{{settings.noticeText}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ClipboardModule } from "ngx-clipboard/dist";
|
|||
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { CouchPotatoService, JobService, RadarrService, SonarrService, TesterService, ValidationService } from "../services";
|
||||
import { CouchPotatoService, EmbyService, IssuesService, JobService, PlexService, RadarrService, SonarrService, TesterService, ValidationService } from "../services";
|
||||
|
||||
import { PipeModule } from "../pipes/pipe.module";
|
||||
import { AboutComponent } from "./about/about.component";
|
||||
|
@ -16,6 +16,7 @@ import { CouchPotatoComponent } from "./couchpotato/couchpotato.component";
|
|||
import { CustomizationComponent } from "./customization/customization.component";
|
||||
import { DogNzbComponent } from "./dognzb/dognzb.component";
|
||||
import { EmbyComponent } from "./emby/emby.component";
|
||||
import { IssuesComponent } from "./issues/issues.component";
|
||||
import { JobsComponent } from "./jobs/jobs.component";
|
||||
import { LandingPageComponent } from "./landingpage/landingpage.component";
|
||||
import { DiscordComponent } from "./notifications/discord.component";
|
||||
|
@ -40,28 +41,29 @@ import { SettingsMenuComponent } from "./settingsmenu.component";
|
|||
import { AutoCompleteModule, CalendarModule, InputSwitchModule, InputTextModule, MenuModule, RadioButtonModule, TooltipModule } from "primeng/primeng";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "Settings/Ombi", component: OmbiComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/About", component: AboutComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Plex", component: PlexComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Emby", component: EmbyComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Sonarr", component: SonarrComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Radarr", component: RadarrComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/LandingPage", component: LandingPageComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Customization", component: CustomizationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Email", component: EmailNotificationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Discord", component: DiscordComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Slack", component: SlackComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Pushover", component: PushoverComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Update", component: UpdateComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/CouchPotato", component: CouchPotatoComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/DogNzb", component: DogNzbComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Telegram", component: TelegramComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Jobs", component: JobsComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/SickRage", component: SickRageComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Settings/Authentication", component: AuthenticationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Ombi", component: OmbiComponent, canActivate: [AuthGuard] },
|
||||
{ path: "About", component: AboutComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Plex", component: PlexComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Emby", component: EmbyComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Sonarr", component: SonarrComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Radarr", component: RadarrComponent, canActivate: [AuthGuard] },
|
||||
{ path: "LandingPage", component: LandingPageComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Customization", component: CustomizationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Email", component: EmailNotificationComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Discord", component: DiscordComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Slack", component: SlackComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Pushover", component: PushoverComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
|
||||
{ path: "UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Update", component: UpdateComponent, canActivate: [AuthGuard] },
|
||||
{ path: "CouchPotato", component: CouchPotatoComponent, canActivate: [AuthGuard] },
|
||||
{ path: "DogNzb", component: DogNzbComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Telegram", component: TelegramComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Jobs", component: JobsComponent, canActivate: [AuthGuard] },
|
||||
{ path: "SickRage", component: SickRageComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Issues", component: IssuesComponent, canActivate: [AuthGuard] },
|
||||
{ path: "Authentication", component: AuthenticationComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -107,6 +109,7 @@ const routes: Routes = [
|
|||
DogNzbComponent,
|
||||
SickRageComponent,
|
||||
TelegramComponent,
|
||||
IssuesComponent,
|
||||
AuthenticationComponent,
|
||||
],
|
||||
exports: [
|
||||
|
@ -121,6 +124,9 @@ const routes: Routes = [
|
|||
TesterService,
|
||||
JobService,
|
||||
CouchPotatoService,
|
||||
IssuesService,
|
||||
PlexService,
|
||||
EmbyService,
|
||||
],
|
||||
|
||||
})
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<ul class="dropdown-menu">
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Customization']">Customization</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/LandingPage']">Landing Page</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Issues']">Issues</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/UserManagement']">User Importer</a></li>
|
||||
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Authentication']">Authentication</a></li>
|
||||
</ul>
|
||||
|
|
26
src/Ombi/ClientApp/app/shared/issues-report.component.html
Normal file
26
src/Ombi/ClientApp/app/shared/issues-report.component.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<p-sidebar [(visible)]="visible" position="right" styleClass="ui-sidebar-md side-back" (onHide)="hide()">
|
||||
<div *ngIf="title">
|
||||
|
||||
<h3>Reporting an Issue for "{{title}}"</h3>
|
||||
|
||||
<h4 *ngIf="issueCategory">Issue type: {{issueCategory.value}}</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="subject" class="control-label">Subject</label>
|
||||
<div>
|
||||
<input type="text" [(ngModel)]="issue.subject" class="form-control form-control-custom " id="subject" name="subject"
|
||||
value="{{issue?.subject}}">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description" class="control-label">Descriptiopn</label>
|
||||
<div>
|
||||
<textarea class="form-control-custom form-control" [(ngModel)]="issue.description" rows="5" type="text"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button type="button" [disabled]="submitted" class="btn btn-primary-outline" (click)="submit()">Submit</button>
|
||||
</div>
|
||||
</p-sidebar>
|
74
src/Ombi/ClientApp/app/shared/issues-report.component.ts
Normal file
74
src/Ombi/ClientApp/app/shared/issues-report.component.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
|
||||
import { IIssueCategory, IIssues, IssueStatus, RequestType } from "./../interfaces";
|
||||
import { IssuesService, NotificationService } from "./../services";
|
||||
|
||||
@Component({
|
||||
selector: "issue-report",
|
||||
templateUrl: "issues-report.component.html",
|
||||
|
||||
})
|
||||
export class IssuesReportComponent {
|
||||
@Input() public visible: boolean;
|
||||
@Input() public id: number; // RequestId
|
||||
@Input() public title: string;
|
||||
@Input() public issueCategory: IIssueCategory;
|
||||
@Input() public movie: boolean;
|
||||
@Input() public providerId: string;
|
||||
|
||||
@Output() public visibleChange = new EventEmitter<boolean>();
|
||||
|
||||
public submitted: boolean = false;
|
||||
|
||||
get getTitle(): string {
|
||||
return this.title;
|
||||
}
|
||||
|
||||
public issue: IIssues;
|
||||
|
||||
constructor(private issueService: IssuesService,
|
||||
private notification: NotificationService) {
|
||||
this.issue = {
|
||||
subject: "",
|
||||
description: "",
|
||||
issueCategory: { value: "", id: 0 },
|
||||
status: IssueStatus.Pending,
|
||||
resolvedDate: undefined,
|
||||
id: undefined,
|
||||
issueCategoryId: 0,
|
||||
comments: [],
|
||||
requestId: undefined,
|
||||
requestType: RequestType.movie,
|
||||
title: "",
|
||||
providerId: "",
|
||||
userReported: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
public submit() {
|
||||
this.submitted = true;
|
||||
const issue = this.issue;
|
||||
issue.requestId = this.id;
|
||||
issue.issueCategory = this.issueCategory;
|
||||
issue.issueCategoryId = this.issueCategory.id;
|
||||
issue.title = this.title;
|
||||
issue.providerId = this.providerId;
|
||||
if (this.movie) {
|
||||
issue.requestType = RequestType.movie;
|
||||
} else {
|
||||
issue.requestType = RequestType.tvShow;
|
||||
}
|
||||
this.issueService.createIssue(issue).subscribe(x => {
|
||||
if (x) {
|
||||
this.notification.success("Issue Created");
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.submitted = false;
|
||||
this.visible = !this.visible;
|
||||
this.visibleChange.emit(this.visible);
|
||||
}
|
||||
}
|
|
@ -3,11 +3,25 @@ import { NgModule } from "@angular/core";
|
|||
import { FormsModule } from "@angular/forms";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
|
||||
import { IssuesReportComponent } from "./issues-report.component";
|
||||
|
||||
import { SidebarModule } from "primeng/primeng";
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
IssuesReportComponent,
|
||||
],
|
||||
imports: [
|
||||
SidebarModule,
|
||||
FormsModule,
|
||||
CommonModule,
|
||||
],
|
||||
exports: [
|
||||
TranslateModule,
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
SidebarModule,
|
||||
IssuesReportComponent,
|
||||
],
|
||||
})
|
||||
export class SharedModule {}
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<td>
|
||||
{{u.lastLoggedIn | date: 'short'}}
|
||||
</td>
|
||||
<td ng-hide="hideColumns">
|
||||
<td>
|
||||
<span *ngIf="u.userType === 1">Local User</span>
|
||||
<span *ngIf="u.userType === 2">Plex User</span>
|
||||
<span *ngIf="u.userType === 3">Emby User</span>
|
||||
|
|
|
@ -17,10 +17,10 @@ import { IdentityService } from "../services";
|
|||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "usermanagement", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||
{ path: "usermanagement/add", component: UserManagementAddComponent, canActivate: [AuthGuard] },
|
||||
{ path: "usermanagement/edit/:id", component: UserManagementEditComponent, canActivate: [AuthGuard] },
|
||||
{ path: "usermanagement/updatedetails", component: UpdateDetailsComponent, canActivate: [AuthGuard] },
|
||||
{ path: "", component: UserManagementComponent, canActivate: [AuthGuard] },
|
||||
{ path: "add", component: UserManagementAddComponent, canActivate: [AuthGuard] },
|
||||
{ path: "edit/:id", component: UserManagementEditComponent, canActivate: [AuthGuard] },
|
||||
{ path: "updatedetails", component: UpdateDetailsComponent, canActivate: [AuthGuard] },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -16,13 +16,12 @@ import { PlexService } from "../services";
|
|||
import { IdentityService } from "../services";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "Wizard", component: WelcomeComponent},
|
||||
{ path: "Wizard/MediaServer", component: MediaServerComponent},
|
||||
{ path: "Wizard/Plex", component: PlexComponent},
|
||||
{ path: "Wizard/Emby", component: EmbyComponent},
|
||||
{ path: "Wizard/CreateAdmin", component: CreateAdminComponent},
|
||||
{ path: "", component: WelcomeComponent},
|
||||
{ path: "MediaServer", component: MediaServerComponent},
|
||||
{ path: "Plex", component: PlexComponent},
|
||||
{ path: "Emby", component: EmbyComponent},
|
||||
{ path: "CreateAdmin", component: CreateAdminComponent},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
|
|
@ -24,4 +24,3 @@ $bg-colour-disabled: #252424;
|
|||
background: $primary-colour !important;
|
||||
color: white;
|
||||
}*/
|
||||
|
||||
|
|
|
@ -326,3 +326,12 @@ button.list-group-item:focus {
|
|||
.ui-radiobutton, .ui-radiobutton-label{
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.side-back {
|
||||
padding-top:5%;
|
||||
box-shadow: 0px 0px 3.5em #000000;
|
||||
}
|
||||
|
||||
.ui-widget-overlay .ui-sidebar-mask {
|
||||
background: black;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ $info-colour: #5bc0de;
|
|||
$warning-colour: #f0ad4e;
|
||||
$danger-colour: #d9534f;
|
||||
$success-colour: #5cb85c;
|
||||
$i:!important;
|
||||
$i: !important;
|
||||
|
||||
@media (min-width: 768px ) {
|
||||
.row {
|
||||
|
@ -312,6 +312,10 @@ $border-radius: 10px;
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-search-results .no-search-results-icon {
|
||||
font-size: 10em;
|
||||
color: $form-color;
|
||||
|
@ -851,3 +855,7 @@ a > h4:hover {
|
|||
-webkit-text-stroke-color: black;
|
||||
font-size: 0.9rem !important
|
||||
}
|
||||
|
||||
.card {
|
||||
padding-top:15px;
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace Ombi.Controllers.External
|
|||
public class RadarrController : Controller
|
||||
{
|
||||
public RadarrController(IRadarrApi radarr, ISettingsService<RadarrSettings> settings,
|
||||
IMemoryCache mem)
|
||||
ICacheService mem)
|
||||
{
|
||||
RadarrApi = radarr;
|
||||
RadarrSettings = settings;
|
||||
|
@ -27,7 +27,7 @@ namespace Ombi.Controllers.External
|
|||
|
||||
private IRadarrApi RadarrApi { get; }
|
||||
private ISettingsService<RadarrSettings> RadarrSettings { get; }
|
||||
private IMemoryCache Cache { get; }
|
||||
private ICacheService Cache { get; }
|
||||
/// <summary>
|
||||
/// Gets the Radarr profiles.
|
||||
/// </summary>
|
||||
|
@ -58,16 +58,15 @@ namespace Ombi.Controllers.External
|
|||
[HttpGet("Profiles")]
|
||||
public async Task<IEnumerable<RadarrProfile>> GetProfiles()
|
||||
{
|
||||
return await Cache.GetOrCreate(CacheKeys.RadarrQualityProfiles, async entry =>
|
||||
return await Cache.GetOrAdd(CacheKeys.RadarrQualityProfiles, async () =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
|
||||
var settings = await RadarrSettings.GetSettingsAsync();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
return await RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}, DateTime.Now.AddHours(1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -78,16 +77,15 @@ namespace Ombi.Controllers.External
|
|||
[HttpGet("RootFolders")]
|
||||
public async Task<IEnumerable<RadarrRootFolder>> GetRootFolders()
|
||||
{
|
||||
return await Cache.GetOrCreate(CacheKeys.RadarrRootProfiles, async entry =>
|
||||
return await Cache.GetOrAdd(CacheKeys.RadarrRootProfiles, async () =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
|
||||
var settings = await RadarrSettings.GetSettingsAsync();
|
||||
if (settings.Enabled)
|
||||
{
|
||||
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}, DateTime.Now.AddHours(1));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ namespace Ombi.Controllers
|
|||
public class ImagesController : Controller
|
||||
{
|
||||
public ImagesController(IFanartTvApi api, IApplicationConfigRepository config,
|
||||
IOptions<LandingPageBackground> options, IMemoryCache c)
|
||||
IOptions<LandingPageBackground> options, ICacheService c)
|
||||
{
|
||||
Api = api;
|
||||
Config = config;
|
||||
|
@ -29,16 +29,12 @@ namespace Ombi.Controllers
|
|||
private IFanartTvApi Api { get; }
|
||||
private IApplicationConfigRepository Config { get; }
|
||||
private LandingPageBackground Options { get; }
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ICacheService _cache;
|
||||
|
||||
[HttpGet("tv/{tvdbid}")]
|
||||
public async Task<string> GetTvBanner(int tvdbid)
|
||||
{
|
||||
var key = await _cache.GetOrCreateAsync(CacheKeys.FanartTv, async entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromDays(1);
|
||||
return await Config.Get(Store.Entities.ConfigurationTypes.FanartTv);
|
||||
});
|
||||
var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1));
|
||||
|
||||
var images = await Api.GetTvImages(tvdbid, key.Value);
|
||||
if (images.tvbanner != null)
|
||||
|
@ -62,11 +58,7 @@ namespace Ombi.Controllers
|
|||
var movieUrl = string.Empty;
|
||||
var tvUrl = string.Empty;
|
||||
|
||||
var key = await _cache.GetOrCreateAsync(CacheKeys.FanartTv, async entry =>
|
||||
{
|
||||
entry.SlidingExpiration = TimeSpan.FromDays(1);
|
||||
return await Config.Get(Store.Entities.ConfigurationTypes.FanartTv);
|
||||
});
|
||||
var key = await _cache.GetOrAdd(CacheKeys.FanartTv, async () => await Config.Get(Store.Entities.ConfigurationTypes.FanartTv), DateTime.Now.AddDays(1));
|
||||
|
||||
if (moviesArray.Any())
|
||||
{
|
||||
|
|
219
src/Ombi/Controllers/IssuesController.cs
Normal file
219
src/Ombi/Controllers/IssuesController.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Ombi.Store.Entities.Requests;
|
||||
using Ombi.Store.Repository;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Hangfire;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Ombi.Attributes;
|
||||
using Ombi.Core;
|
||||
using Ombi.Core.Notifications;
|
||||
using Ombi.Helpers;
|
||||
using Ombi.Models;
|
||||
using Ombi.Notifications.Models;
|
||||
using Ombi.Store.Entities;
|
||||
|
||||
namespace Ombi.Controllers
|
||||
{
|
||||
[ApiV1]
|
||||
[Authorize]
|
||||
[Produces("application/json")]
|
||||
public class IssuesController : Controller
|
||||
{
|
||||
public IssuesController(IRepository<IssueCategory> categories, IRepository<Issues> issues, IRepository<IssueComments> comments,
|
||||
UserManager<OmbiUser> userManager, INotificationService notify)
|
||||
{
|
||||
_categories = categories;
|
||||
_issues = issues;
|
||||
_issueComments = comments;
|
||||
_userManager = userManager;
|
||||
_notification = notify;
|
||||
}
|
||||
|
||||
private readonly IRepository<IssueCategory> _categories;
|
||||
private readonly IRepository<Issues> _issues;
|
||||
private readonly IRepository<IssueComments> _issueComments;
|
||||
private readonly UserManager<OmbiUser> _userManager;
|
||||
private readonly INotificationService _notification;
|
||||
|
||||
/// <summary>
|
||||
/// Get's all categories
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("categories")]
|
||||
public async Task<IEnumerable<IssueCategory>> Categories()
|
||||
{
|
||||
return await _categories.GetAll().ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new category
|
||||
/// </summary>
|
||||
/// <param name="cat"></param>
|
||||
/// <returns></returns>
|
||||
[PowerUser]
|
||||
[HttpPost("categories")]
|
||||
public async Task<bool> CreateCategory([FromBody]IssueCategory cat)
|
||||
{
|
||||
var result = await _categories.Add(cat);
|
||||
if (result.Id > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a Category
|
||||
/// </summary>
|
||||
/// <param name="catId"></param>
|
||||
/// <returns></returns>
|
||||
[PowerUser]
|
||||
[HttpDelete("categories/{catId}")]
|
||||
public async Task<bool> DeleteCategory([FromRoute]int catId)
|
||||
{
|
||||
var cat = await _categories.GetAll().FirstOrDefaultAsync(x => x.Id == catId);
|
||||
await _categories.Delete(cat);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the issues
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public async Task<IEnumerable<Issues>> GetIssues()
|
||||
{
|
||||
return await _issues.GetAll().Include(x => x.IssueCategory).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the issues
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{take}/{skip}/{status}")]
|
||||
public async Task<IEnumerable<Issues>> GetIssues(int take, int skip, IssueStatus status)
|
||||
{
|
||||
return await _issues
|
||||
.GetAll()
|
||||
.Where(x => x.Status == status)
|
||||
.Include(x => x.IssueCategory)
|
||||
.Include(x => x.UserReported)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all the issues count
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("count")]
|
||||
public async Task<IssueCountModel> GetIssueCount()
|
||||
{
|
||||
return new IssueCountModel
|
||||
{
|
||||
Pending = await _issues.GetAll().Where(x => x.Status == IssueStatus.Pending).CountAsync(),
|
||||
InProgress = await _issues.GetAll().Where(x => x.Status == IssueStatus.InProgress).CountAsync(),
|
||||
Resolved = await _issues.GetAll().Where(x => x.Status == IssueStatus.Resolved).CountAsync()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Movie Issue
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<int> CreateIssue([FromBody]Issues i)
|
||||
{
|
||||
i.IssueCategory = null;
|
||||
i.UserReportedId = (await _userManager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name)).Id;
|
||||
await _issues.Add(i);
|
||||
|
||||
var notificationModel = new NotificationOptions
|
||||
{
|
||||
RequestId = 0,
|
||||
DateTime = DateTime.Now,
|
||||
NotificationType = NotificationType.Issue,
|
||||
RequestType = i.RequestType,
|
||||
Recipient = string.Empty,
|
||||
AdditionalInformation = $"{i.Subject} | {i.Description}"
|
||||
};
|
||||
|
||||
BackgroundJob.Enqueue(() => _notification.Publish(notificationModel));
|
||||
|
||||
return i.Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the issue by Id
|
||||
/// </summary>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<Issues> GetIssue([FromRoute] int id)
|
||||
{
|
||||
return await _issues.GetAll().Where(x => x.Id == id)
|
||||
.Include(x => x.IssueCategory)
|
||||
.Include(x => x.UserReported)
|
||||
.FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get's all the issue comments by id
|
||||
/// </summary>
|
||||
[HttpGet("{id}/comments")]
|
||||
public async Task<IEnumerable<IssueCommentChatViewModel>> GetComments([FromRoute]int id)
|
||||
{
|
||||
var comment = await _issueComments.GetAll().Where(x => x.IssuesId == id).Include(x => x.User).ToListAsync();
|
||||
var vm = new List<IssueCommentChatViewModel>();
|
||||
|
||||
foreach (var c in comment)
|
||||
{
|
||||
var roles = await _userManager.GetRolesAsync(c.User);
|
||||
vm.Add(new IssueCommentChatViewModel
|
||||
{
|
||||
Comment = c.Comment,
|
||||
Date = c.Date,
|
||||
Username = c.User.UserAlias,
|
||||
AdminComment = roles.Contains(OmbiRoles.PowerUser) || roles.Contains(OmbiRoles.Admin)
|
||||
});
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a comment on an issue
|
||||
/// </summary>
|
||||
[HttpPost("comments")]
|
||||
public async Task<IssueComments> AddComment([FromBody] NewIssueCommentViewModel comment)
|
||||
{
|
||||
var userId = await _userManager.Users.Where(x => User.Identity.Name == x.UserName).Select(x => x.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
var newComment = new IssueComments
|
||||
{
|
||||
Comment = comment.Comment,
|
||||
Date = DateTime.UtcNow,
|
||||
UserId = userId,
|
||||
IssuesId = comment.IssueId,
|
||||
};
|
||||
return await _issueComments.Add(newComment);
|
||||
}
|
||||
|
||||
[HttpPost("status")]
|
||||
public async Task<bool> UpdateStatus([FromBody] IssueStateViewModel model)
|
||||
{
|
||||
var issue = await _issues.Find(model.IssueId);
|
||||
if (issue == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
issue.Status = model.Status;
|
||||
await _issues.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ namespace Ombi.Controllers
|
|||
public class JobController : Controller
|
||||
{
|
||||
public JobController(IOmbiAutomaticUpdater updater, IPlexUserImporter userImporter,
|
||||
IMemoryCache mem, IEmbyUserImporter embyImporter, IPlexContentSync plexContentSync,
|
||||
ICacheService mem, IEmbyUserImporter embyImporter, IPlexContentSync plexContentSync,
|
||||
IEmbyContentSync embyContentSync)
|
||||
{
|
||||
_updater = updater;
|
||||
|
@ -33,7 +33,7 @@ namespace Ombi.Controllers
|
|||
private readonly IOmbiAutomaticUpdater _updater;
|
||||
private readonly IPlexUserImporter _plexUserImporter;
|
||||
private readonly IEmbyUserImporter _embyUserImporter;
|
||||
private readonly IMemoryCache _memCache;
|
||||
private readonly ICacheService _memCache;
|
||||
private readonly IPlexContentSync _plexContentSync;
|
||||
private readonly IEmbyContentSync _embyContentSync;
|
||||
|
||||
|
@ -74,9 +74,8 @@ namespace Ombi.Controllers
|
|||
[HttpGet("updateCached")]
|
||||
public async Task<bool> CheckForUpdateCached()
|
||||
{
|
||||
var val = await _memCache.GetOrCreateAsync(CacheKeys.Update, async entry =>
|
||||
var val = await _memCache.GetOrAdd(CacheKeys.Update, async () =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTimeOffset.Now.AddHours(1);
|
||||
var productArray = _updater.GetVersion();
|
||||
var version = productArray[0];
|
||||
var branch = productArray[1];
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace Ombi.Controllers
|
|||
INotificationTemplatesRepository templateRepo,
|
||||
IEmbyApi embyApi,
|
||||
IRadarrSync radarrSync,
|
||||
IMemoryCache memCache,
|
||||
ICacheService memCache,
|
||||
IGithubApi githubApi)
|
||||
{
|
||||
SettingsResolver = resolver;
|
||||
|
@ -73,7 +73,7 @@ namespace Ombi.Controllers
|
|||
private INotificationTemplatesRepository TemplateRepository { get; }
|
||||
private readonly IEmbyApi _embyApi;
|
||||
private readonly IRadarrSync _radarrSync;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ICacheService _cache;
|
||||
private readonly IGithubApi _githubApi;
|
||||
|
||||
/// <summary>
|
||||
|
@ -479,6 +479,36 @@ namespace Ombi.Controllers
|
|||
return await Save(settings);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Save the Issues settings.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("Issues")]
|
||||
public async Task<bool> IssueSettings([FromBody]IssueSettings settings)
|
||||
{
|
||||
return await Save(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Issues Settings.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet("Issues")]
|
||||
public async Task<IssueSettings> IssueSettings()
|
||||
{
|
||||
return await Get<IssueSettings>();
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("issuesenabled")]
|
||||
public async Task<bool> IssuesEnabled()
|
||||
{
|
||||
var issues = await Get<IssueSettings>();
|
||||
return issues.Enabled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves the email notification settings.
|
||||
/// </summary>
|
||||
|
|
|
@ -33,8 +33,15 @@ namespace Ombi
|
|||
|
||||
//if (exception is NotFoundException) code = HttpStatusCode.NotFound;
|
||||
if (exception is UnauthorizedAccessException) code = HttpStatusCode.Unauthorized;
|
||||
|
||||
var result = JsonConvert.SerializeObject(new { error = exception.Message });
|
||||
string result;
|
||||
if (exception.InnerException != null)
|
||||
{
|
||||
result = JsonConvert.SerializeObject(new { error = exception.InnerException.Message });
|
||||
}
|
||||
else
|
||||
{
|
||||
result = JsonConvert.SerializeObject(new { error = exception.Message });
|
||||
}
|
||||
context.Response.ContentType = "application/json";
|
||||
context.Response.StatusCode = (int)code;
|
||||
return context.Response.WriteAsync(result);
|
||||
|
|
12
src/Ombi/Models/IssueCommentChatViewModel.cs
Normal file
12
src/Ombi/Models/IssueCommentChatViewModel.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace Ombi.Models
|
||||
{
|
||||
public class IssueCommentChatViewModel
|
||||
{
|
||||
public string Comment { get; set; }
|
||||
public DateTime Date { get; set; }
|
||||
public string Username { get; set; }
|
||||
public bool AdminComment { get; set; }
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue