Damn son. So many changes... Fixed alot of stuff around tv episodes with the new DB model #865

pull/1488/head
tidusjar 7 years ago
parent 1a7f81b16c
commit 0875b5f665

@ -1,110 +1,110 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Moq;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Requests.Movie;
using Ombi.Core.Models.Requests.Tv;
using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models;
using Ombi.Core.Rule.Rules.Search;
using Xunit;
namespace Ombi.Core.Tests.Rule.Search
{
public class ExistignRequestRuleTests
{
public ExistignRequestRuleTests()
{
MovieMock = new Mock<IRequestService<MovieRequestModel>>();
TvMock = new Mock<IRequestService<TvRequestModel>>();
Rule = new ExistingRequestRule(MovieMock.Object, TvMock.Object);
}
private ExistingRequestRule Rule { get; }
private Mock<IRequestService<MovieRequestModel>> MovieMock { get; }
private Mock<IRequestService<TvRequestModel>> TvMock { get; }
[Fact]
public async Task ShouldBe_Requested_WhenExisitngMovie()
{
var list = new List<MovieRequestModel>{new MovieRequestModel
{
ProviderId = 123,
Approved = true
}};
MovieMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
var search = new SearchMovieViewModel
{
Id = 123,
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.Equal(search.Approved, true);
}
[Fact]
public async Task ShouldBe_NotRequested_WhenNewMovie()
{
var list = new List<MovieRequestModel>{new MovieRequestModel
{
ProviderId = 123,
Approved = true
}};
MovieMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
var search = new SearchMovieViewModel
{
Id = 999,
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.Equal(search.Approved, false);
}
[Fact]
public async Task ShouldBe_Requested_WhenExisitngTv()
{
var list = new List<TvRequestModel>{new TvRequestModel
{
ProviderId = 123,
Approved = true
}};
TvMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
var search = new SearchTvShowViewModel
{
Id = 123,
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.Equal(search.Approved, true);
}
[Fact]
public async Task ShouldBe_NotRequested_WhenNewTv()
{
var list = new List<TvRequestModel>{new TvRequestModel
{
ProviderId = 123,
Approved = true
}};
TvMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
var search = new SearchTvShowViewModel()
{
Id = 999,
};
var result = await Rule.Execute(search);
Assert.True(result.Success);
Assert.Equal(search.Approved, false);
}
}
}
//using System.Collections.Generic;
//using System.Threading.Tasks;
//using Moq;
//using Ombi.Core.Models.Requests;
//using Ombi.Core.Models.Requests.Movie;
//using Ombi.Core.Models.Requests.Tv;
//using Ombi.Core.Models.Search;
//using Ombi.Core.Requests.Models;
//using Ombi.Core.Rule.Rules.Search;
//using Xunit;
//namespace Ombi.Core.Tests.Rule.Search
//{
// public class ExistignRequestRuleTests
// {
// public ExistignRequestRuleTests()
// {
// MovieMock = new Mock<IRequestService<MovieRequestModel>>();
// TvMock = new Mock<IRequestService<TvRequestModel>>();
// Rule = new ExistingRequestRule(MovieMock.Object, TvMock.Object);
// }
// private ExistingRequestRule Rule { get; }
// private Mock<IRequestService<MovieRequestModel>> MovieMock { get; }
// private Mock<IRequestService<TvRequestModel>> TvMock { get; }
// [Fact]
// public async Task ShouldBe_Requested_WhenExisitngMovie()
// {
// var list = new List<MovieRequestModel>{new MovieRequestModel
// {
// ProviderId = 123,
// Approved = true
// }};
// MovieMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
// var search = new SearchMovieViewModel
// {
// Id = 123,
// };
// var result = await Rule.Execute(search);
// Assert.True(result.Success);
// Assert.Equal(search.Approved, true);
// }
// [Fact]
// public async Task ShouldBe_NotRequested_WhenNewMovie()
// {
// var list = new List<MovieRequestModel>{new MovieRequestModel
// {
// ProviderId = 123,
// Approved = true
// }};
// MovieMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
// var search = new SearchMovieViewModel
// {
// Id = 999,
// };
// var result = await Rule.Execute(search);
// Assert.True(result.Success);
// Assert.Equal(search.Approved, false);
// }
// [Fact]
// public async Task ShouldBe_Requested_WhenExisitngTv()
// {
// var list = new List<TvRequestModel>{new TvRequestModel
// {
// ProviderId = 123,
// Approved = true
// }};
// TvMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
// var search = new SearchTvShowViewModel
// {
// Id = 123,
// };
// var result = await Rule.Execute(search);
// Assert.True(result.Success);
// Assert.Equal(search.Approved, true);
// }
// [Fact]
// public async Task ShouldBe_NotRequested_WhenNewTv()
// {
// var list = new List<TvRequestModel>{new TvRequestModel
// {
// ProviderId = 123,
// Approved = true
// }};
// TvMock.Setup(x => x.GetAllAsync()).ReturnsAsync(list);
// var search = new SearchTvShowViewModel()
// {
// Id = 999,
// };
// var result = await Rule.Execute(search);
// Assert.True(result.Success);
// Assert.Equal(search.Approved, false);
// }
// }
//}

@ -1,19 +1,11 @@
using System;
using Ombi.Core.Claims;
using Ombi.Core.Models.Requests;
using Ombi.Core.Claims;
using Ombi.Core.Rule;
using Ombi.Core.Rules;
using Ombi.Store.Entities;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Hangfire;
using Ombi.Core.Models.Requests.Movie;
using Ombi.Core.Models.Search;
using Ombi.Core.Notifications;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Engine.Interfaces

@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Requests.Tv;
using Ombi.Core.Models.Search;
using Ombi.Store.Entities.Requests;

@ -1,10 +1,8 @@
using Ombi.Api.TheMovieDb;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Requests.Movie;
using Ombi.Core.Models.Search;
using Ombi.Core.Rules;
using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Store.Entities;
using System;
using System.Collections.Generic;

@ -1,16 +1,12 @@
using AutoMapper;
using Hangfire;
using Ombi.Api.TvMaze;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Rules;
using Ombi.Helpers;
using Ombi.Notifications;
using Ombi.Notifications.Models;
using Ombi.Store.Entities;
using System;
using System.Collections.Generic;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.Linq;
using System.Security.Principal;
@ -18,7 +14,6 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.IdentityResolver;
using Ombi.Core.Models.Requests.Tv;
using Ombi.Core.Rule;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
@ -50,7 +45,7 @@ namespace Ombi.Core.Engine
// For some reason the poster path is always http
var posterPath = showInfo.image?.medium.Replace("http:", "https:");
var tvRequests = new List<SeasonRequestModel>();
var tvRequests = new List<SeasonRequests>();
// Only have the TV requests we actually requested and not everything
foreach (var season in tv.SeasonRequests)
{
@ -84,7 +79,44 @@ namespace Ombi.Core.Engine
SeasonRequests = new List<SeasonRequests>()
};
if (tv.LatestSeason)
if (tv.RequestAll)
{
var episodes = await TvApi.EpisodeLookup(showInfo.id);
var seasonRequests = new List<SeasonRequests>();
foreach (var ep in episodes)
{
var episodesRequests = new List<EpisodeRequests>();
var season = childRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == ep.season);
if (season == null)
{
childRequest.SeasonRequests.Add(new SeasonRequests
{
Episodes = new List<EpisodeRequests>{
new EpisodeRequests
{
EpisodeNumber = ep.number,
AirDate = DateTime.Parse(ep.airdate),
Title = ep.name,
Url = ep.url
}
},
SeasonNumber = ep.season,
});
}
else
{
season.Episodes.Add(new EpisodeRequests
{
EpisodeNumber = ep.number,
AirDate = DateTime.Parse(ep.airdate),
Title = ep.name,
Url = ep.url
});
}
}
}
else if (tv.LatestSeason)
{
var episodes = await TvApi.EpisodeLookup(showInfo.id);
var latest = episodes.OrderBy(x => x.season).FirstOrDefault();
@ -105,8 +137,7 @@ namespace Ombi.Core.Engine
SeasonNumber = latest.season,
});
}
if (tv.FirstSeason)
else if (tv.FirstSeason)
{
var episodes = await TvApi.EpisodeLookup(showInfo.id);
var first = episodes.OrderByDescending(x => x.season).FirstOrDefault();
@ -130,7 +161,12 @@ namespace Ombi.Core.Engine
SeasonNumber = first.season,
});
}
else
{
// It's a custom request
childRequest.SeasonRequests = tvRequests;
}
var ruleResults = await RunRequestRules(childRequest);
var results = ruleResults as RuleResult[] ?? ruleResults.ToArray();
if (results.Any(x => !x.Success))
@ -144,6 +180,35 @@ namespace Ombi.Core.Engine
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.Id);
if (existingRequest != null)
{
// Remove requests we already have, we just want new ones
var existingSeasons = existingRequest.ChildRequests.Select(x => x.SeasonRequests);
foreach (var existingSeason in existingRequest.ChildRequests)
foreach (var existing in existingSeason.SeasonRequests)
{
var newChild = childRequest.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == existing.SeasonNumber);
if (newChild != null)
{
// We have some requests in this season...
// Let's find the episodes.
foreach (var existingEp in existing.Episodes)
{
var duplicateEpisode = newChild.Episodes.FirstOrDefault(x => x.EpisodeNumber == existingEp.EpisodeNumber);
if (duplicateEpisode != null)
{
// Remove it.
newChild.Episodes.Remove(duplicateEpisode);
}
}
if (!newChild.Episodes.Any())
{
// We may have removed all episodes
childRequest.SeasonRequests.Remove(newChild);
}
}
}
// Remove the ID since this is a new child
childRequest.Id = 0;
return await AddExistingRequest(childRequest, existingRequest);
}
// This is a new request
@ -190,7 +255,7 @@ namespace Ombi.Core.Engine
results = Mapper.Map<TvRequests>(request);
// TODO need to check if we need to approve any child requests since they may have updated
await TvRepository.Update(results);
return results;
}
@ -215,47 +280,6 @@ namespace Ombi.Core.Engine
return await AfterRequest(newRequest);
}
private IEnumerable<SeasonRequestModel> GetListDifferences(List<SeasonRequestModel> existing,
List<SeasonRequestModel> request)
{
var requestsToRemove = new List<SeasonRequestModel>();
foreach (var r in request)
{
// Do we have an existing season?
var existingSeason = existing.FirstOrDefault(x => x.SeasonNumber == r.SeasonNumber);
if (existingSeason == null)
{
continue;
}
// Compare the episodes
for (var i = r.Episodes.Count - 1; i >= 0; i--)
{
var existingEpisode = existingSeason.Episodes.FirstOrDefault(x => x.EpisodeNumber == r.Episodes[i].EpisodeNumber);
if (existingEpisode == null)
{
// we are fine, we have not yet requested this
}
else
{
// We already have this request
r.Episodes.RemoveAt(i);
}
}
if (!r.Episodes.Any())
{
requestsToRemove.Add(r);
}
}
foreach (var remove in requestsToRemove)
{
request.Remove(remove);
}
return request;
}
private async Task<RequestEngineResult> AddRequest(TvRequests model)
{
await TvRepository.Add(model);

@ -15,8 +15,8 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests.Tv;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository.Requests;
namespace Ombi.Core.Engine
{
@ -64,29 +64,32 @@ namespace Ombi.Core.Engine
var season = mapped.SeasonRequests.FirstOrDefault(x => x.SeasonNumber == e.season);
if (season == null)
{
var newSeason = new SeasonRequestModel
var newSeason = new SeasonRequests
{
SeasonNumber = e.season,
Episodes = new List<EpisodeRequests>()
};
newSeason.Episodes.Add(new EpisodesRequested
newSeason.Episodes.Add(new EpisodeRequests
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp),
EpisodeNumber = e.number,
});
mapped.SeasonRequests.Add(newSeason);
}
//else
//{
// // Find the episode
// var ep = episodes.FirstOrDefault(x => x.number == e.number);
// ep.Url = e.url;
// ep.Title = e.name;
// ep.AirDate = DateTime.Parse(e.airstamp);
// ep.EpisodeNumber = e.number;
//}
else
{
// We already have the season, so just add the episode
season.Episodes.Add(new EpisodeRequests
{
Url = e.url,
Title = e.name,
AirDate = DateTime.Parse(e.airstamp),
EpisodeNumber = e.number,
});
}
}
var existingRequests = await GetTvRequests();

@ -1,5 +1,4 @@
using System.Threading.Tasks;
using Ombi.Core.Models.Requests.Movie;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core

@ -1,40 +0,0 @@
using Newtonsoft.Json;
using Ombi.Store.Entities;
using System;
namespace Ombi.Core.Models.Requests
{
public class BaseRequestModel : Entity
{
public int ProviderId { get; set; }
public string Overview { get; set; }
public string Title { get; set; }
public string PosterPath { get; set; }
public DateTime ReleaseDate { get; set; }
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
public bool Admin { get; set; }
public DateTime RequestedDate { get; set; }
public bool Available { get; set; }
public IssueState Issues { get; set; }
public string OtherMessage { get; set; }
public string AdminNote { get; set; }
public string RequestedUser { get; set; }
public int IssueId { get; set; }
public bool Denied { get; set; }
public string DeniedReason { get; set; }
[JsonIgnore]
public bool Released => DateTime.UtcNow > ReleaseDate;
[JsonIgnore]
public bool CanApprove => !Approved && !Available;
public bool UserHasRequested(string username)
{
return RequestedUser.Equals(username, StringComparison.OrdinalIgnoreCase);
}
}
}

@ -1,42 +0,0 @@
using Ombi.Core.Models.Requests;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Ombi.Core.Requests.Models
{
public interface IRequestService<T> where T : BaseRequestModel
{
int AddRequest(T model);
Task<int> AddRequestAsync(T model);
void BatchDelete(IEnumerable<T> model);
void BatchUpdate(IEnumerable<T> model);
T CheckRequest(int providerId);
Task<T> CheckRequestAsync(int providerId);
void DeleteRequest(T request);
Task DeleteRequestAsync(int request);
Task DeleteRequestAsync(T request);
T Get(int id);
IEnumerable<T> GetAll();
Task<IEnumerable<T>> GetAllAsync();
Task<IEnumerable<T>> GetAllAsync(int count, int position);
Task<T> GetAsync(int id);
T UpdateRequest(T model);
IQueryable<T> GetAllQueryable();
}
}

@ -0,0 +1,12 @@
namespace Ombi.Core.Models.Requests
{
public enum IssueState
{
None = 99,
WrongAudio = 0,
NoSubtitles = 1,
WrongContent = 2,
PlaybackIssues = 3,
Other = 4 // Provide a message
}
}

@ -1,212 +0,0 @@
using Ombi.Core.Requests.Models;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests.Tv;
namespace Ombi.Core.Models.Requests
{
public class JsonRequestService<T> : IRequestService<T> where T : BaseRequestModel
{
public JsonRequestService(IRequestRepository repo)
{
Repo = repo;
RequestType = typeof(T) == typeof(TvRequestModel) ? RequestType.TvShow : RequestType.Movie;
}
private RequestType RequestType { get; }
private IRequestRepository Repo { get; }
public int AddRequest(T model)
{
var entity = new RequestBlobs
{
Type = model.Type,
Content = ByteConverterHelper.ReturnBytes(model),
ProviderId = model.ProviderId
};
var id = Repo.Insert(entity);
return id.Id;
}
public async Task<int> AddRequestAsync(T model)
{
var entity = new RequestBlobs
{
Type = model.Type,
Content = ByteConverterHelper.ReturnBytes(model),
ProviderId = model.ProviderId
};
var id = await Repo.InsertAsync(entity).ConfigureAwait(false);
return id.Id;
}
public T CheckRequest(int providerId)
{
var blobs = Repo.GetAll();
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId && x.Type == RequestType);
if (blob == null)
return null;
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id = blob.Id;
return model;
}
public async Task<T> CheckRequestAsync(int providerId)
{
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId && x.Type == RequestType);
if (blob == null)
return null;
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id = blob.Id;
return model;
}
public void DeleteRequest(T request)
{
var blob = Repo.Get(request.Id);
Repo.Delete(blob);
}
public async Task DeleteRequestAsync(T request)
{
var blob = await Repo.GetAsync(request.Id).ConfigureAwait(false);
Repo.Delete(blob);
}
public async Task DeleteRequestAsync(int request)
{
var blob = await Repo.GetAsync(request).ConfigureAwait(false);
Repo.Delete(blob);
}
public T UpdateRequest(T model)
{
var b = Repo.Get(model.Id);
b.Content = ByteConverterHelper.ReturnBytes(model);
var blob = Repo.Update(b);
return model;
}
public T Get(int id)
{
var blob = Repo.Get(id);
if (blob == null)
return default(T);
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id =
blob
.Id; // They should always be the same, but for somereason a user didn't have it in the db https://github.com/tidusjar/Ombi/issues/862#issuecomment-269743847
return model;
}
public async Task<T> GetAsync(int id)
{
var blob = await Repo.GetAsync(id).ConfigureAwait(false);
if (blob == null)
return default(T);
var model = ByteConverterHelper.ReturnObject<T>(blob.Content);
model.Id = blob.Id;
return model;
}
public IEnumerable<T> GetAll()
{
var blobs = Repo.GetAll().Where(x => x.Type == RequestType).ToList();
var retVal = new List<T>();
foreach (var b in blobs)
{
if (b == null)
continue;
var model = ByteConverterHelper.ReturnObject<T>(b.Content);
model.Id = b.Id;
retVal.Add(model);
}
return retVal;
}
public IQueryable<T> GetAllQueryable()
{
var retVal = new List<T>();
var blobs = Repo.GetAllQueryable();
foreach (var b in blobs)
{
if (b == null)
continue;
var model = ByteConverterHelper.ReturnObject<T>(b.Content);
model.Id = b.Id;
retVal.Add(model);
}
return retVal.AsQueryable();
}
public async Task<IEnumerable<T>> GetAllAsync()
{
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
var retVal = new List<T>();
foreach (var b in blobs.Where(x => x.Type == RequestType))
{
if (b == null)
continue;
var model = ByteConverterHelper.ReturnObject<T>(b.Content);
model.Id = b.Id;
retVal.Add(model);
}
return retVal;
}
public async Task<IEnumerable<T>> GetAllAsync(int count, int position)
{
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
var retVal = new List<T>();
foreach (var b in blobs.Where(x => x.Type == RequestType).Skip(position).Take(count))
{
if (b == null)
continue;
var model = ByteConverterHelper.ReturnObject<T>(b.Content);
model.Id = b.Id;
retVal.Add(model);
}
return retVal;
}
public void BatchUpdate(IEnumerable<T> model)
{
var entities = model
.Select(m => new RequestBlobs
{
Type = m.Type,
Content = ByteConverterHelper.ReturnBytes(m),
ProviderId = m.ProviderId,
Id = m.Id
})
.ToList();
Repo.UpdateAll(entities);
}
public void BatchDelete(IEnumerable<T> model)
{
var entities = model
.Select(m => new RequestBlobs
{
Type = m.Type,
Content = ByteConverterHelper.ReturnBytes(m),
ProviderId = m.ProviderId,
Id = m.Id
})
.ToList();
Repo.DeleteAll(entities);
}
}
}

@ -1,7 +0,0 @@
namespace Ombi.Core.Models.Requests.Movie
{
public class MovieRequestModel : BaseRequestModel
{
public string ImdbId { get; set; }
}
}

@ -1,61 +0,0 @@
using Ombi.Store.Entities;
using System;
using System.Collections.Generic;
namespace Ombi.Core.Models.Requests
{
public enum IssueState
{
None = 99,
WrongAudio = 0,
NoSubtitles = 1,
WrongContent = 2,
PlaybackIssues = 3,
Other = 4 // Provide a message
}
public class EpisodesModel : IEquatable<EpisodesModel>
{
public int SeasonNumber { get; set; }
public int EpisodeNumber { get; set; }
public bool Equals(EpisodesModel other)
{
// Check whether the compared object is null.
if (ReferenceEquals(other, null)) return false;
//Check whether the compared object references the same data.
if (ReferenceEquals(this, other)) return true;
//Check whether the properties are equal.
return SeasonNumber.Equals(other.SeasonNumber) && EpisodeNumber.Equals(other.EpisodeNumber);
}
public override int GetHashCode()
{
var hashSeason = SeasonNumber.GetHashCode();
var hashEp = EpisodeNumber.GetHashCode();
//Calculate the hash code.
return hashSeason + hashEp;
}
}
public class SeasonRequestModel
{
public int SeasonNumber { get; set; }
public List<EpisodesRequested> Episodes { get; set; } = new List<EpisodesRequested>();
}
public class EpisodesRequested
{
public int EpisodeNumber { get; set; }
public string Title { get; set; }
public DateTime AirDate { get; set; }
public string Url { get; set; }
public bool Requested { get; set; }
public string Status { get; set; }
public bool Available { get; set; }
public bool Approved { get; set; }
}
}

@ -1,33 +0,0 @@
using Ombi.Store.Entities;
using System;
namespace Ombi.Core.Models.Requests
{
public class RequestViewModel
{
public int Id { get; set; }
public int ProviderId { get; set; }
public string ImdbId { get; set; }
public string Overview { get; set; }
public string Title { get; set; }
public string PosterPath { get; set; }
public DateTime ReleaseDate { get; set; }
public bool Released { get; set; }
public RequestType Type { get; set; }
public string Status { get; set; }
public bool Approved { get; set; }
public string[] RequestedUsers { get; set; }
public DateTime RequestedDate { get; set; }
public string ReleaseYear { get; set; }
public bool Available { get; set; }
public bool Admin { get; set; }
public int IssueId { get; set; }
public QualityModel[] Qualities { get; set; }
public EpisodesModel[] Episodes { get; set; }
public bool Denied { get; set; }
public string DeniedReason { get; set; }
public RootFolderModel[] RootFolders { get; set; }
public bool HasRootFolders { get; set; }
public string CurrentRootPath { get; set; }
}
}

@ -1,31 +0,0 @@
using System.Collections.Generic;
namespace Ombi.Core.Models.Requests.Tv
{
public class TvRequestModel : BaseRequestModel
{
public TvRequestModel()
{
ChildRequests = new List<ChildTvRequest>();
}
public string ImdbId { get; set; }
public string TvDbId { get; set; }
public List<ChildTvRequest> ChildRequests { get; set; }
/// <summary>
/// For TV Shows with a custom root folder
/// </summary>
/// <value>
/// The root folder selected.
/// </value>
public int RootFolderSelected { get; set; }
}
public class ChildTvRequest : BaseRequestModel
{
public bool RequestAll { get; set; }
public List<SeasonRequestModel> SeasonRequests { get; set; } = new List<SeasonRequestModel>();
}
}

@ -1,4 +1,5 @@
using Ombi.Core.Models.Requests;
using Ombi.Store.Repository.Requests;
using System.Collections.Generic;
namespace Ombi.Core.Models.Search
@ -39,7 +40,7 @@ namespace Ombi.Core.Models.Search
/// </value>
public string Homepage { get; set; }
public List<SeasonRequestModel> SeasonRequests { get; set; } = new List<SeasonRequestModel>();
public List<SeasonRequests> SeasonRequests { get; set; } = new List<SeasonRequests>();
/// <summary>
/// If we are requesting the entire series

@ -1,10 +1,8 @@
using Ombi.Core.Models.Requests.Movie;
using Ombi.Core.Settings;
using Ombi.Core.Settings;
using Ombi.Settings.Settings.Models.External;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Radarr;
using Ombi.Core.Models.Requests;
using Ombi.Helpers;
using Ombi.Store.Entities.Requests;

@ -30,4 +30,9 @@
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.Api.TheMovieDb.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Models\Requests\Movie\" />
<Folder Include="Models\Requests\Tv\" />
</ItemGroup>
</Project>

@ -1,11 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Requests.Movie;
using Ombi.Core.Models.Requests.Tv;
using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;

@ -17,7 +17,6 @@ using Ombi.Core.Engine.Interfaces;
using Ombi.Core.IdentityResolver;
using Ombi.Core.Models.Requests;
using Ombi.Core.Notifications;
using Ombi.Core.Requests.Models;
using Ombi.Core.Rule;
using Ombi.Core.Settings;
using Ombi.Notifications;
@ -75,7 +74,6 @@ namespace Ombi.DependencyInjection
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
services.AddScoped<IOmbiContext, OmbiContext>();
services.AddTransient<IRequestRepository, RequestJsonRepository>();
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ISettingsResolver, SettingsResolver>();
@ -89,7 +87,6 @@ namespace Ombi.DependencyInjection
public static void RegisterServices(this IServiceCollection services)
{
services.AddTransient<IRequestServiceMain, RequestService>();
services.AddTransient(typeof(IRequestService<>), typeof(JsonRequestService<>));
services.AddSingleton<INotificationService, NotificationService>();
services.AddTransient<INotificationHelper, NotificationHelper>();

@ -7,6 +7,7 @@ using Ombi.Core.Models.Search;
using Ombi.Helpers;
using TraktApiSharp.Objects.Get.Shows;
using TraktApiSharp.Objects.Get.Shows.Common;
using Ombi.Store.Repository.Requests;
//using TraktApiSharp.Objects.Get.Shows;
//using TraktApiSharp.Objects.Get.Shows.Common;
@ -51,23 +52,22 @@ namespace Ombi.Mapping.Profiles
CreateMap<TvMazeCustomSeason, SeasonRequestModel>()
.ConstructUsing(x =>
{
var season = new SeasonRequestModel
{
SeasonNumber = x.SeasonNumber
};
foreach (var ep in x.EpisodeNumber)
{
season.Episodes.Add(new EpisodesRequested
{
EpisodeNumber = ep,
});
}
return season;
});
//CreateMap<TvMazeCustomSeason, SeasonRequests>()
// .ConstructUsing(x =>
// {
// var season = new SeasonRequests
// {
// SeasonNumber = x.SeasonNumber
// };
// foreach (var ep in x.EpisodeNumber)
// {
// season.Episodes.Add(new EpisodeRequests
// {
// EpisodeNumber = ep,
// });
// }
// return season;
// });

@ -13,7 +13,6 @@ namespace Ombi.Store.Context
{
int SaveChanges();
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
DbSet<RequestBlobs> Requests { get; set; }
DbSet<GlobalSettings> Settings { get; set; }
DbSet<PlexContent> PlexContent { get; set; }
DbSet<RadarrCache> RadarrCache { get; set; }

@ -34,8 +34,7 @@ namespace Ombi.Store.Context
// Add the notifcation templates
AddAllTemplates();
}
public DbSet<RequestBlobs> Requests { get; set; }
public DbSet<GlobalSettings> Settings { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<PlexContent> PlexContent { get; set; }

@ -1,18 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("RequestBlobs")]
public class RequestBlobs : Entity
{
public int ProviderId { get; set; }
public byte[] Content { get; set; }
public RequestType Type { get; set; }
}
public enum RequestType
{
Movie = 1,
TvShow = 2
}
}

@ -1,15 +0,0 @@
using System;
namespace Ombi.Store.Entities
{
public class RequestHistory : Entity
{
public int UserId { get; set; }
public RequestType Type { get; set; }
public DateTime RequestedDate { get; set; }
public int RequestId { get; set; }
public virtual RequestBlobs Request { get; set; }
public virtual User User { get; set; }
}
}

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Ombi.Store.Entities
{
public enum RequestType
{
TvShow,
Movie
}
}

@ -24,8 +24,10 @@ namespace Ombi.Store.Repository.Requests
public DateTime AirDate { get; set; }
public string Url { get; set; }
public bool Available { get; set; }
public bool Approved { get; set; }
public bool Requested { get; set; }
public int SeasonId { get; set; }
[ForeignKey(nameof(SeasonId))]
public SeasonRequests Season { get; set; }

@ -1,22 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface IRequestRepository
{
void Delete(RequestBlobs entity);
void DeleteAll(IEnumerable<RequestBlobs> entity);
RequestBlobs Get(int id);
IEnumerable<RequestBlobs> GetAll();
Task<IEnumerable<RequestBlobs>> GetAllAsync();
Task<RequestBlobs> GetAsync(int id);
RequestBlobs Insert(RequestBlobs entity);
Task<RequestBlobs> InsertAsync(RequestBlobs entity);
RequestBlobs Update(RequestBlobs entity);
void UpdateAll(IEnumerable<RequestBlobs> entity);
IQueryable<RequestBlobs> GetAllQueryable();
}
}

@ -1,140 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Store.Context;
using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public class RequestJsonRepository : IRequestRepository
{
//private ICacheProvider Cache { get; }
public RequestJsonRepository(IOmbiContext ctx)
{
Db = ctx;
}
private IOmbiContext Db { get; }
public RequestBlobs Insert(RequestBlobs entity)
{
var id = Db.Requests.Add(entity);
Db.SaveChanges();
return id.Entity;
}
public async Task<RequestBlobs> InsertAsync(RequestBlobs entity)
{
var id = await Db.Requests.AddAsync(entity).ConfigureAwait(false);
await Db.SaveChangesAsync();
return id.Entity;
}
public IEnumerable<RequestBlobs> GetAll()
{
//var key = "GetAll";
//var item = Cache.GetOrSet(key, () =>
//{
var page = Db.Requests.ToList();
return page;
//}, 5);
//return item;
}
public async Task<IEnumerable<RequestBlobs>> GetAllAsync()
{
//var key = "GetAll";
//var item = await Cache.GetOrSetAsync(key, async () =>
//{
var page = await Db.Requests.ToListAsync().ConfigureAwait(false);
return page;
//}, 5);
//return item;
}
public RequestBlobs Get(int id)
{
//var key = "Get" + id;
//var item = Cache.GetOrSet(key, () =>
//{
var page = Db.Requests.Find(id);
return page;
//}, 5);
//return item;
}
public async Task<RequestBlobs> GetAsync(int id)
{
//var key = "Get" + id;
//var item = await Cache.GetOrSetAsync(key, async () =>
//{
var page = await Db.Requests.FindAsync(id).ConfigureAwait(false);
return page;
//}, 5);
//return item;
}
public void Delete(RequestBlobs entity)
{
//ResetCache();
Db.Requests.Remove(entity);
Db.SaveChanges();
}
public RequestBlobs Update(RequestBlobs entity)
{
try
{
Db.SaveChanges();
return entity;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public void UpdateAll(IEnumerable<RequestBlobs> entity)
{
Db.Requests.UpdateRange(entity);
Db.SaveChanges();
}
public void DeleteAll(IEnumerable<RequestBlobs> entity)
{
Db.Requests.RemoveRange(entity);
Db.SaveChanges();
}
public IQueryable<RequestBlobs> GetAllQueryable()
{
return Db.Requests.AsQueryable();
}
}
}

@ -167,6 +167,8 @@ CREATE TABLE IF NOT EXISTS EpisodeRequests (
Url VARCHAR(100) NOT NULL,
SeasonId INTEGER NOT NULL,
Available INTEGER NOT NULL,
Requested INTEGER NOT NULL,
Approved INTEGER NOT NULL,
FOREIGN KEY (SeasonId) REFERENCES SeasonRequests(Id)

@ -138,5 +138,7 @@ export interface IEpisodesRequests {
airDate: Date,
url: string,
available: boolean,
requested: boolean,
approved: boolean,
}

@ -8,7 +8,6 @@
userType: UserType,
}
export enum UserType {
LocalUser = 1,
PlexUser = 2,

@ -17,7 +17,7 @@
<div class="row">
<div class="col-sm-2">
<img class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
<img class="img-responsive poster" src="https://image.tmdb.org/t/p/w150/{{request.posterPath}}" alt="poster">
</div>

@ -17,7 +17,7 @@
<div class="row">
<div class="col-sm-2">
<img class="img-responsive" src="{{request.posterPath}}" alt="poster">
<img class="img-responsive poster" src="{{request.posterPath}}" alt="poster">
</div>
@ -187,52 +187,85 @@
<p-dialog *ngIf="showChildDialogue" id="dialogOverride" header="{{selectedSeason?.title}}" [(visible)]="showChildDialogue" [contentStyle]="{'overflow':'auto','max-height':'500px'}" [width]="768">
<div class="row" *ngFor="let child of selectedSeason.childRequests">
<div class="col-md-12">
<!--Child Requests-->
<div class="col-md-9">
<span class="col-md-12">Requested By: <b>{{child.requestedUser.username}}</b></span>
<div class="modal fade in" *ngIf="showChildDialogue" style="display: block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" (click)="showChildDialogue=false">&times;</button>
<h4 class="modal-title">{{selectedSeason?.title}}</h4>
<!--<span class="col-md-12" *ngIf="child.requestAll">Requested All Seasons</span>-->
<!--Seasons-->
<span *ngIf="child.approved && !child.available" class="label label-info">Processing Request</span>
<span *ngIf="child.denied" class="label label-danger">Request Denied</span>
<span *ngIf="child.deniedReason" title="{{child.deniedReason}}"><i class="fa fa-info-circle"></i></span>
<span *ngIf="!child.approved && !child.availble && !child.denied" class="label label-warning">Pending Approval</span>
<div class="col-md-12" *ngFor="let seasons of child.seasonRequests">
<span><b>Season: {{seasons.seasonNumber}}</b></span>
<div>
<span>Episodes:</span>
<div *ngFor="let episode of seasons.episodes">
<!--Episodes-->
<span># {{episode.episodeNumber}} | {{episode.title}}</span>
<span *ngIf="episode.available" class="label label-success">Available</span>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<button *ngIf="!child.approved" type="button" (click)="approveSeasonRequest(child)" class="btn btn-sm btn-success-outline" style="text-align: right"><i class="fa fa-plus"></i> Approve</button>
<button *ngIf="child.approved && !child.available" type="button" [disabled]="true" class="btn btn-sm btn-primary-outline" style="text-align: right"><i class="fa fa-plus"></i> Processing</button>
<button *ngIf="!child.approved && !child.available && !child.denied" type="button" (click)="denySeasonRequest(child)" class="btn btn-sm btn-danger-outline" style="text-align: right"><i class="fa fa-plus"></i> Deny</button>
</div>
<div class="modal-body">
<div class="row" *ngFor="let child of selectedSeason.childRequests">
<div class="col-md-12">
<!--Child Requests-->
<div class="col-md-9">
<span class="col-md-12">Requested By: <b>{{child.requestedUser}}</b></span>
<!--<span class="col-md-12" *ngIf="child.requestAll">Requested All Seasons</span>-->
<!--Seasons-->
<span *ngIf="child.approved && !child.available" class="label label-info">Processing Request</span>
<span *ngIf="child.denied" class="label label-danger">Request Denied</span>
<span *ngIf="child.deniedReason" title="{{child.deniedReason}}"><i class="fa fa-info-circle"></i></span>
<span *ngIf="!child.approved && !child.availble && !child.denied" class="label label-warning">Pending Approval</span>
<div class="col-md-12" *ngFor="let seasons of child.seasonRequests">
<span>Season: {{seasons.seasonNumber}}</span>
<span>Episodes:</span>
<div *ngFor="let episode of seasons.episodes">
<!--Episodes-->
<span># {{episode.episodeNumber}} | {{episode.title}}</span>
<span *ngIf="episode.available" class="label label-success">Available</span>
</div>
<hr />
</div>
</p-dialog>
<!--<div class="modal fade in" *ngIf="showChildDialogue" style="display: block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" (click)="showChildDialogue=false">&times;</button>
<h4 class="modal-title">{{selectedSeason?.title}}</h4>
</div>
<div class="modal-body">
<div class="row" *ngFor="let child of selectedSeason.childRequests">
<div class="col-md-12">
<div class="col-md-9">
<span class="col-md-12">Requested By: <b>{{child.requestedUser.username}}</b></span>
<span *ngIf="child.approved && !child.available" class="label label-info">Processing Request</span>
<span *ngIf="child.denied" class="label label-danger">Request Denied</span>
<span *ngIf="child.deniedReason" title="{{child.deniedReason}}"><i class="fa fa-info-circle"></i></span>
<span *ngIf="!child.approved && !child.availble && !child.denied" class="label label-warning">Pending Approval</span>
<div class="col-md-12" *ngFor="let seasons of child.seasonRequests">
<span>Season: {{seasons.seasonNumber}}</span>
<span>Episodes:</span>
<div *ngFor="let episode of seasons.episodes">
<span># {{episode.episodeNumber}} | {{episode.title}}</span>
<span *ngIf="episode.available" class="label label-success">Available</span>
</div>
</div>
</div>
<div class="col-md-3">
<button *ngIf="!child.approved" type="button" (click)="approveSeasonRequest(child)" class="btn btn-sm btn-success-outline" style="text-align: right"><i class="fa fa-plus"></i> Approve</button>
<button *ngIf="child.approved && !child.available" type="button" [disabled]="true" class="btn btn-sm btn-primary-outline" style="text-align: right"><i class="fa fa-plus"></i> Processing</button>
<button *ngIf="!child.approved && !child.available && !child.denied" type="button" (click)="denySeasonRequest(child)" class="btn btn-sm btn-danger-outline" style="text-align: right"><i class="fa fa-plus"></i> Deny</button>
</div>
</div>
<div class="col-md-3">
<button *ngIf="!child.approved" type="button" (click)="approveSeasonRequest(child)" class="btn btn-sm btn-success-outline" style="text-align: right"><i class="fa fa-plus"></i> Approve</button>
<button *ngIf="!child.approved && !child.available && !child.denied" type="button" (click)="denySeasonRequest(child)" class="btn btn-sm btn-danger-outline" style="text-align: right"><i class="fa fa-plus"></i> Deny</button>
</div>
<hr />
</div>
<hr />
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" (click)="showChildDialogue=false">Close</button>
<div class="modal-footer">
<button type="button" class="btn btn-danger-outline" (click)="showChildDialogue=false">Close</button>
</div>
</div>
</div>
</div>
</div>
</div>-->

@ -31,7 +31,7 @@
<div id="{{id}}imgDiv" class="col-sm-2">
<img *ngIf="result.posterPath" class="img-responsive" src="https://image.tmdb.org/t/p/w150/{{result.posterPath}}" alt="poster">
<img *ngIf="result.posterPath" class="img-responsive poster" src="https://image.tmdb.org/t/p/w150/{{result.posterPath}}" alt="poster">
</div>
<div class="col-sm-8">

@ -10,7 +10,7 @@ import { NotificationService } from '../services/notification.service';
import { ISearchTvResult } from '../interfaces/ISearchTvResult';
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
import { IEpisodesRequested } from "../interfaces/IRequestModel";
import { IEpisodesRequests } from "../interfaces/IRequestModel";
@Component({
templateUrl: './seriesinformation.component.html',
@ -33,7 +33,7 @@ export class SeriesInformationComponent implements OnInit, OnDestroy {
private seriesId: number;
public series: ISearchTvResult;
requestedEpisodes: IEpisodesRequested[] = [];
requestedEpisodes: IEpisodesRequests[] = [];
ngOnInit(): void {
@ -61,7 +61,7 @@ export class SeriesInformationComponent implements OnInit, OnDestroy {
});
}
public addRequest(episode: IEpisodesRequested) {
public addRequest(episode: IEpisodesRequests) {
episode.requested = true;
}

@ -37,7 +37,7 @@
<div class="row">
<div class="col-sm-2">
<img *ngIf="result.banner" class="img-responsive" width="150" [src]="result.banner" alt="poster">
<img *ngIf="result.banner" class="img-responsive poster" width="150" [src]="result.banner" alt="poster">
</div>
<div class="col-sm-8">

@ -1,9 +1,9 @@
using System.Collections.Generic;
using Ombi.Core.Models.Requests;
using Ombi.Store.Entities.Requests;
namespace Ombi.Models
{
public class RequestGridModel<T> where T : BaseRequestModel
public class RequestGridModel<T> where T : BaseRequest
{
public IEnumerable<T> Available { get; set; }
public IEnumerable<T> New { get; set; }

@ -14,3 +14,14 @@ $bg-colour-disabled: #252424;
background: $primary-colour !important;
color: white;
}
#dialogOverride > div > .ui-dialog-content {
border: 1px solid $bg-colour-disabled !important;
background: $bg-colour !important;
color: white;
}
/*#dialogOverride > div > .ui-dialog-titlebar {
background: $primary-colour !important;
color: white;
}*/

@ -734,4 +734,9 @@ body {
textarea {
resize: vertical;
}
.poster {
box-shadow: 5px 5px 30px #000000;
border-radius: 30px;
}
Loading…
Cancel
Save