mirror of https://github.com/Ombi-app/Ombi
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
479 lines
19 KiB
479 lines
19 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using Nancy;
|
|
using Nancy.Responses.Negotiation;
|
|
using NLog;
|
|
using Ombi.Api;
|
|
using Ombi.Core;
|
|
using Ombi.Core.Models;
|
|
using Ombi.Core.SettingModels;
|
|
using Ombi.Helpers;
|
|
using Ombi.Helpers.Permissions;
|
|
using Ombi.Services.Interfaces;
|
|
using Ombi.Services.Notification;
|
|
using Ombi.Store;
|
|
using Ombi.UI.Models;
|
|
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
|
|
|
|
namespace Ombi.UI.Modules
|
|
{
|
|
public class IssuesModule : BaseAuthModule
|
|
{
|
|
public IssuesModule(ISettingsService<PlexRequestSettings> pr, IIssueService issueService, IRequestService request, INotificationService n, ISecurityExtensions security) : base("issues", pr, security)
|
|
{
|
|
IssuesService = issueService;
|
|
RequestService = request;
|
|
NotificationService = n;
|
|
|
|
Get["/"] = x => Index();
|
|
|
|
Get["/{id}", true] = async (x, ct) => await Details(x.id);
|
|
|
|
Post["/issue", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
|
|
|
|
Get["/pending", true] = async (x, ct) => await GetIssues(IssueStatus.PendingIssue);
|
|
Get["/resolved", true] = async (x, ct) => await GetIssues(IssueStatus.ResolvedIssue);
|
|
|
|
Post["/remove", true] = async (x, ct) => await RemoveIssue((int)Request.Form.issueId);
|
|
Post["/resolvedUpdate", true] = async (x, ct) => await ChangeStatus((int)Request.Form.issueId, IssueStatus.ResolvedIssue);
|
|
|
|
Post["/clear", true] = async (x, ct) => await ClearIssue((int)Request.Form.issueId, (IssueState)(int)Request.Form.issue);
|
|
|
|
Get["/issuecount", true] = async (x, ct) => await IssueCount();
|
|
Get["/tabCount", true] = async (x, ct) => await TabCount();
|
|
|
|
Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.providerId, IssueState.Other, (string)Request.Form.commentArea);
|
|
|
|
Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null);
|
|
|
|
Post["/nonrequestissuecomment", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, IssueState.Other, (string)Request.Form.commentArea);
|
|
|
|
|
|
Post["/addnote", true] = async (x, ct) => await AddNote((int)Request.Form.requestId, (string)Request.Form.noteArea, (IssueState)(int)Request.Form.issue);
|
|
}
|
|
|
|
private IIssueService IssuesService { get; }
|
|
private IRequestService RequestService { get; }
|
|
private INotificationService NotificationService { get; }
|
|
private static Logger Log = LogManager.GetCurrentClassLogger();
|
|
|
|
public Negotiator Index()
|
|
{
|
|
return View["Index"];
|
|
}
|
|
|
|
private async Task<Response> GetIssues(IssueStatus status)
|
|
{
|
|
var issues = await IssuesService.GetAllAsync();
|
|
issues = await FilterIssuesAsync(issues, status == IssueStatus.ResolvedIssue);
|
|
|
|
var issuesModels = issues as IssuesModel[] ?? issues.Where(x => x.IssueStatus == status).ToArray();
|
|
var viewModel = new List<IssuesViewModel>();
|
|
|
|
foreach (var i in issuesModels)
|
|
{
|
|
var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), Admin = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests)
|
|
};
|
|
|
|
// Create a string with all of the current issue states with a "," delimiter in e.g. Wrong Content, Playback Issues
|
|
var state = i.Issues.Select(x => x.Issue).ToArray();
|
|
var issueState = string.Empty;
|
|
for (var j = 0; j < state.Length; j++)
|
|
{
|
|
var word = state[j].ToString().ToCamelCaseWords();
|
|
if (j != state.Length - 1)
|
|
{
|
|
issueState += $"{word}, ";
|
|
}
|
|
else
|
|
{
|
|
issueState += word;
|
|
}
|
|
}
|
|
model.Issues = issueState;
|
|
|
|
viewModel.Add(model);
|
|
}
|
|
|
|
return Response.AsJson(viewModel);
|
|
}
|
|
|
|
public async Task<Response> IssueCount()
|
|
{
|
|
var issues = await IssuesService.GetAllAsync();
|
|
|
|
var myIssues = await FilterIssuesAsync(issues);
|
|
|
|
var count = myIssues.Count();
|
|
|
|
return Response.AsJson(count);
|
|
}
|
|
|
|
public async Task<Response> TabCount()
|
|
{
|
|
var issues = await IssuesService.GetAllAsync();
|
|
|
|
var myIssues = await FilterIssuesAsync(issues);
|
|
|
|
var count = new List<object>();
|
|
|
|
var issuesModels = myIssues as IssuesModel[] ?? myIssues.ToArray();
|
|
var pending = issuesModels.Where(x => x.IssueStatus == IssueStatus.PendingIssue);
|
|
var resolved = issuesModels.Where(x => x.IssueStatus == IssueStatus.ResolvedIssue);
|
|
|
|
count.Add(new { Name = IssueStatus.PendingIssue, Count = pending.Count() });
|
|
count.Add(new { Name = IssueStatus.ResolvedIssue, Count = resolved.Count() });
|
|
|
|
return Response.AsJson(count);
|
|
}
|
|
|
|
public async Task<Negotiator> Details(int id)
|
|
{
|
|
var issue = await IssuesService.GetAsync(id);
|
|
if (issue == null)
|
|
return Index();
|
|
|
|
issue = Order(issue);
|
|
var m = new IssuesDetailsViewModel
|
|
{
|
|
Issues = issue.Issues,
|
|
RequestId = issue.RequestId,
|
|
Title = issue.Title,
|
|
IssueStatus = issue.IssueStatus,
|
|
Deleted = issue.Deleted,
|
|
Type = issue.Type,
|
|
ProviderId = issue.ProviderId,
|
|
PosterUrl = issue.PosterUrl.Contains("https://image.tmdb.org/t/p/w150/") ? issue.PosterUrl : $"https://image.tmdb.org/t/p/w150/{issue.PosterUrl}",
|
|
Id = issue.Id
|
|
};
|
|
return View["Details", m];
|
|
}
|
|
|
|
private async Task<Response> ReportRequestIssue(int requestId, IssueState issue, string comment)
|
|
{
|
|
|
|
var model = new IssueModel
|
|
{
|
|
Issue = issue,
|
|
UserReported = Username,
|
|
UserNote = !string.IsNullOrEmpty(comment)
|
|
? $"{Username} - {comment}"
|
|
: string.Empty,
|
|
};
|
|
|
|
var request = await RequestService.GetAsync(requestId);
|
|
|
|
var issueEntity = await IssuesService.GetAllAsync();
|
|
var existingIssue = issueEntity.FirstOrDefault(x => x.RequestId == requestId);
|
|
|
|
var notifyModel = new NotificationModel
|
|
{
|
|
User = Username,
|
|
NotificationType = NotificationType.Issue,
|
|
Title = request.Title,
|
|
DateTime = DateTime.Now,
|
|
Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
|
|
};
|
|
|
|
// An issue already exists
|
|
if (existingIssue != null)
|
|
{
|
|
if (existingIssue.Issues.Any(x => x.Issue == issue))
|
|
{
|
|
return
|
|
Response.AsJson(new JsonResponseModel
|
|
{
|
|
Result = false,
|
|
Message = "This issue has already been reported!"
|
|
});
|
|
|
|
}
|
|
existingIssue.Issues.Add(model);
|
|
var result = await IssuesService.UpdateIssueAsync(existingIssue);
|
|
|
|
|
|
await NotificationService.Publish(notifyModel);
|
|
|
|
return Response.AsJson(result
|
|
? new JsonResponseModel { Result = true }
|
|
: new JsonResponseModel { Result = false });
|
|
}
|
|
|
|
// New issue
|
|
var issues = new IssuesModel
|
|
{
|
|
Title = request.Title,
|
|
PosterUrl = request.PosterPath,
|
|
RequestId = requestId,
|
|
Type = request.Type,
|
|
IssueStatus = IssueStatus.PendingIssue
|
|
};
|
|
issues.Issues.Add(model);
|
|
|
|
var issueId = await IssuesService.AddIssueAsync(issues);
|
|
|
|
request.IssueId = issueId;
|
|
await RequestService.UpdateRequestAsync(request);
|
|
|
|
await NotificationService.Publish(notifyModel);
|
|
return Response.AsJson(new JsonResponseModel { Result = true });
|
|
}
|
|
|
|
private async Task<Response> ReportNonRequestIssue(int providerId, string type, IssueState issue, string comment)
|
|
{
|
|
var currentIssues = await IssuesService.GetAllAsync();
|
|
var notifyModel = new NotificationModel
|
|
{
|
|
User = Username,
|
|
NotificationType = NotificationType.Issue,
|
|
DateTime = DateTime.Now,
|
|
Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
|
|
};
|
|
var model = new IssueModel
|
|
{
|
|
Issue = issue,
|
|
UserReported = Username,
|
|
UserNote = !string.IsNullOrEmpty(comment)
|
|
? $"{Username} - {comment}"
|
|
: string.Empty,
|
|
};
|
|
|
|
var existing = currentIssues.FirstOrDefault(x => x.ProviderId == providerId && !x.Deleted && x.IssueStatus == IssueStatus.PendingIssue);
|
|
if (existing != null)
|
|
{
|
|
existing.Issues.Add(model);
|
|
await IssuesService.UpdateIssueAsync(existing);
|
|
return Response.AsJson(new JsonResponseModel { Result = true });
|
|
}
|
|
|
|
if (type == "movie")
|
|
{
|
|
var movieApi = new TheMovieDbApi();
|
|
|
|
var result = await movieApi.GetMovieInformation(providerId);
|
|
if (result != null)
|
|
{
|
|
notifyModel.Title = result.Title;
|
|
// New issue
|
|
var issues = new IssuesModel
|
|
{
|
|
Title = result.Title,
|
|
PosterUrl = "https://image.tmdb.org/t/p/w150/" + result.PosterPath,
|
|
ProviderId = providerId,
|
|
Type = RequestType.Movie,
|
|
IssueStatus = IssueStatus.PendingIssue
|
|
};
|
|
issues.Issues.Add(model);
|
|
|
|
var issueId = await IssuesService.AddIssueAsync(issues);
|
|
|
|
await NotificationService.Publish(notifyModel);
|
|
return Response.AsJson(new JsonResponseModel { Result = true });
|
|
}
|
|
}
|
|
|
|
if (type == "tv")
|
|
{
|
|
var tv = new TvMazeApi();
|
|
var result = tv.ShowLookupByTheTvDbId(providerId);
|
|
if (result != null)
|
|
{
|
|
var banner = result.image?.medium;
|
|
if (!string.IsNullOrEmpty(banner))
|
|
{
|
|
banner = banner.Replace("http", "https");
|
|
}
|
|
|
|
notifyModel.Title = result.name;
|
|
// New issue
|
|
var issues = new IssuesModel
|
|
{
|
|
Title = result.name,
|
|
PosterUrl = banner,
|
|
ProviderId = providerId,
|
|
Type = RequestType.TvShow,
|
|
IssueStatus = IssueStatus.PendingIssue
|
|
};
|
|
issues.Issues.Add(model);
|
|
|
|
var issueId = await IssuesService.AddIssueAsync(issues);
|
|
|
|
await NotificationService.Publish(notifyModel);
|
|
return Response.AsJson(new JsonResponseModel { Result = true });
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Album Reports are not supported yet!"});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Filters the issues. Checks to see if we have set <c>UsersCanViewOnlyOwnIssues</c> in the database and filters upon the user logged in and that setting.
|
|
/// </summary>
|
|
/// <param name="issues">The issues.</param>
|
|
private async Task<IEnumerable<IssuesModel>> FilterIssuesAsync(IEnumerable<IssuesModel> issues, bool showResolved = false)
|
|
{
|
|
var settings = await PlexRequestSettings.GetSettingsAsync();
|
|
IEnumerable<IssuesModel> myIssues;
|
|
|
|
// Is the user an Admin? If so show everything
|
|
if (IsAdmin)
|
|
{
|
|
var issuesModels = issues as IssuesModel[] ?? issues.ToArray();
|
|
myIssues = issuesModels.Where(x => x.Deleted == false);
|
|
if (!showResolved)
|
|
{
|
|
myIssues = issuesModels.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
|
|
}
|
|
}
|
|
else if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnIssues)) // The user is not an Admin, do we have the settings to hide them?
|
|
{
|
|
if (!showResolved)
|
|
{
|
|
myIssues =
|
|
issues.Where(
|
|
x =>
|
|
x.Issues.Any(i => i.UserReported.Equals(Username, StringComparison.CurrentCultureIgnoreCase)) && x.Deleted == false
|
|
&& x.IssueStatus != IssueStatus.ResolvedIssue);
|
|
}
|
|
else
|
|
{
|
|
myIssues =
|
|
issues.Where(
|
|
x =>
|
|
x.Issues.Any(i => i.UserReported.Equals(Username, StringComparison.CurrentCultureIgnoreCase)) && x.Deleted == false);
|
|
}
|
|
}
|
|
else // Looks like the user is not an admin and there is no settings set.
|
|
{
|
|
var issuesModels = issues as IssuesModel[] ?? issues.ToArray();
|
|
myIssues = issuesModels.Where(x => x.Deleted == false);
|
|
if (!showResolved)
|
|
{
|
|
myIssues = issuesModels.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
|
|
}
|
|
}
|
|
|
|
return myIssues;
|
|
}
|
|
private async Task<Response> RemoveIssue(int issueId)
|
|
{
|
|
try
|
|
{
|
|
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
|
|
{
|
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to remove an issue." });
|
|
}
|
|
|
|
var issue = await IssuesService.GetAsync(issueId);
|
|
var request = await RequestService.GetAsync(issue.RequestId);
|
|
if (request.Id > 0)
|
|
{
|
|
request.IssueId = 0; // No issue;
|
|
|
|
var result = await RequestService.UpdateRequestAsync(request);
|
|
if (result)
|
|
{
|
|
await IssuesService.DeleteIssueAsync(issueId);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await IssuesService.DeleteIssueAsync(issueId);
|
|
}
|
|
|
|
return Response.AsJson(new JsonResponseModel { Result = true });
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error(e);
|
|
return Response.AsJson(new JsonResponseModel() { Result = false, Message = "Could not delete issue! Check the logs."});
|
|
}
|
|
|
|
}
|
|
|
|
private async Task<Negotiator> ChangeStatus(int issueId, IssueStatus status)
|
|
{
|
|
try
|
|
{
|
|
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
|
|
{
|
|
return View["Index"];
|
|
}
|
|
|
|
|
|
var issue = await IssuesService.GetAsync(issueId);
|
|
issue.IssueStatus = status;
|
|
var result = await IssuesService.UpdateIssueAsync(issue);
|
|
return result ? await Details(issueId) : View["Index"];
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error(e);
|
|
return View["Index"];
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private async Task<Negotiator> ClearIssue(int issueId, IssueState state)
|
|
{
|
|
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
|
|
{
|
|
return View["Index"];
|
|
}
|
|
|
|
var issue = await IssuesService.GetAsync(issueId);
|
|
|
|
var toRemove = issue.Issues.FirstOrDefault(x => x.Issue == state);
|
|
issue.Issues.Remove(toRemove);
|
|
|
|
var result = await IssuesService.UpdateIssueAsync(issue);
|
|
|
|
return result ? await Details(issueId) : View["Index"];
|
|
}
|
|
|
|
private async Task<Response> AddNote(int requestId, string noteArea, IssueState state)
|
|
{
|
|
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
|
|
{
|
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to add a note." });
|
|
}
|
|
|
|
var issue = await IssuesService.GetAsync(requestId);
|
|
if (issue == null)
|
|
{
|
|
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Issue does not exist to add a note!" });
|
|
}
|
|
var toAddNote = issue.Issues.FirstOrDefault(x => x.Issue == state);
|
|
|
|
if (toAddNote != null)
|
|
{
|
|
issue.Issues.Remove(toAddNote);
|
|
toAddNote.AdminNote = noteArea;
|
|
issue.Issues.Add(toAddNote);
|
|
}
|
|
|
|
var result = await IssuesService.UpdateIssueAsync(issue);
|
|
return Response.AsJson(result
|
|
? new JsonResponseModel { Result = true }
|
|
: new JsonResponseModel { Result = false, Message = "Could not update the notes, please try again or check the logs" });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Orders the issues descending by the <see cref="IssueState"/>.
|
|
/// </summary>
|
|
/// <param name="issues">The issues.</param>
|
|
/// <returns></returns>
|
|
private IssuesModel Order(IssuesModel issues)
|
|
{
|
|
issues.Issues = issues.Issues.OrderByDescending(x => x.Issue).ToList();
|
|
return issues;
|
|
}
|
|
}
|
|
}
|