mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-19 04:49:33 -07:00
Started adding requesting
This commit is contained in:
parent
e80f0bc601
commit
b7f63ed1ce
35 changed files with 1034 additions and 51 deletions
|
@ -1,6 +1,6 @@
|
||||||
;https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/
|
;https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/
|
||||||
cd ..
|
cd ..
|
||||||
dotnet restore
|
dotnet restore
|
||||||
dotnet publish -c Release -r win10-x64
|
dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64
|
||||||
|
|
||||||
exit
|
exit
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard1.4</TargetFramework>
|
<TargetFramework>netstandard1.4</TargetFramework>
|
||||||
|
<!--<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>-->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
12
Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs
Normal file
12
Ombi/Ombi.Core/Engine/Interfaces/IRequestEngine.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models.Search;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public interface IRequestEngine
|
||||||
|
{
|
||||||
|
Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model);
|
||||||
|
bool ShouldAutoApprove(RequestType requestType);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,24 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
using Ombi.Core.Models.Search;
|
using Ombi.Core.Models.Search;
|
||||||
|
using Ombi.Core.Requests.Models;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
using Ombi.TheMovieDbApi.Models;
|
using Ombi.TheMovieDbApi.Models;
|
||||||
|
|
||||||
namespace Ombi.Core
|
namespace Ombi.Core.Engine
|
||||||
{
|
{
|
||||||
public class MovieEngine : IMovieEngine
|
public class MovieEngine : IMovieEngine
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public MovieEngine(IRequestService service)
|
||||||
|
{
|
||||||
|
RequestService = service;
|
||||||
|
}
|
||||||
|
private IRequestService RequestService { get; }
|
||||||
public async Task<IEnumerable<SearchMovieViewModel>> ProcessMovieSearch(string search)
|
public async Task<IEnumerable<SearchMovieViewModel>> ProcessMovieSearch(string search)
|
||||||
{
|
{
|
||||||
var api = new TheMovieDbApi.TheMovieDbApi();
|
var api = new TheMovieDbApi.TheMovieDbApi();
|
||||||
|
@ -67,8 +77,8 @@ namespace Ombi.Core
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
var viewMovies = new List<SearchMovieViewModel>();
|
var viewMovies = new List<SearchMovieViewModel>();
|
||||||
var counter = 0;
|
//var counter = 0;
|
||||||
//Dictionary<int, RequestedModel> dbMovies = await RequestedMovies();
|
Dictionary<int, RequestModel> dbMovies = await RequestedMovies();
|
||||||
foreach (var movie in movies)
|
foreach (var movie in movies)
|
||||||
{
|
{
|
||||||
var viewMovie = new SearchMovieViewModel
|
var viewMovie = new SearchMovieViewModel
|
||||||
|
@ -138,14 +148,14 @@ namespace Ombi.Core
|
||||||
// viewMovie.Available = true;
|
// viewMovie.Available = true;
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// if (dbMovies.ContainsKey(movie.Id) && canSee) // compare to the requests db
|
if (dbMovies.ContainsKey(movie.id) /*&& canSee*/) // compare to the requests db
|
||||||
// {
|
{
|
||||||
// var dbm = dbMovies[movie.Id];
|
var dbm = dbMovies[movie.id];
|
||||||
|
|
||||||
// viewMovie.Requested = true;
|
viewMovie.Requested = true;
|
||||||
// viewMovie.Approved = dbm.Approved;
|
viewMovie.Approved = dbm.Approved;
|
||||||
// viewMovie.Available = dbm.Available;
|
viewMovie.Available = dbm.Available;
|
||||||
// }
|
}
|
||||||
// else if (canSee)
|
// else if (canSee)
|
||||||
// {
|
// {
|
||||||
// bool exists = IsMovieInCache(movie, viewMovie.ImdbId);
|
// bool exists = IsMovieInCache(movie, viewMovie.ImdbId);
|
||||||
|
@ -159,5 +169,23 @@ namespace Ombi.Core
|
||||||
}
|
}
|
||||||
return viewMovies;
|
return viewMovies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private long _dbMovieCacheTime = 0;
|
||||||
|
private Dictionary<int, RequestModel> _dbMovies;
|
||||||
|
private async Task<Dictionary<int, RequestModel>> RequestedMovies()
|
||||||
|
{
|
||||||
|
long now = DateTime.Now.Ticks;
|
||||||
|
if (_dbMovies == null || (now - _dbMovieCacheTime) > 10000)
|
||||||
|
{
|
||||||
|
var allResults = await RequestService.GetAllAsync();
|
||||||
|
allResults = allResults.Where(x => x.Type == RequestType.Movie);
|
||||||
|
|
||||||
|
var distinctResults = allResults.DistinctBy(x => x.ProviderId);
|
||||||
|
_dbMovies = distinctResults.ToDictionary(x => x.ProviderId);
|
||||||
|
_dbMovieCacheTime = now;
|
||||||
|
}
|
||||||
|
return _dbMovies;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
232
Ombi/Ombi.Core/Engine/RequestEngine.cs
Normal file
232
Ombi/Ombi.Core/Engine/RequestEngine.cs
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Core.Models.Search;
|
||||||
|
using Ombi.Core.Requests.Models;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.TheMovieDbApi;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public class RequestEngine : IRequestEngine
|
||||||
|
{
|
||||||
|
public RequestEngine(IMovieDbApi movieApi, IRequestService requestService)
|
||||||
|
{
|
||||||
|
MovieApi = movieApi;
|
||||||
|
RequestService = requestService;
|
||||||
|
}
|
||||||
|
private IMovieDbApi MovieApi { get; }
|
||||||
|
private IRequestService RequestService { get; }
|
||||||
|
public async Task<RequestEngineResult> RequestMovie(SearchMovieViewModel model)
|
||||||
|
{
|
||||||
|
var movieInfo = await MovieApi.GetMovieInformation(model.Id);
|
||||||
|
if (movieInfo == null)
|
||||||
|
{
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
RequestAdded = false,
|
||||||
|
Message = "There was an issue adding this movie!"
|
||||||
|
};
|
||||||
|
//Response.AsJson(new JsonResponseModel
|
||||||
|
//{
|
||||||
|
// Result = false,
|
||||||
|
// Message = "There was an issue adding this movie!"
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
var fullMovieName =
|
||||||
|
$"{movieInfo.title}{(!string.IsNullOrEmpty(movieInfo.release_date) ? $" ({DateTime.Parse(movieInfo.release_date).Year})" : string.Empty)}";
|
||||||
|
|
||||||
|
var existingRequest = await RequestService.CheckRequestAsync(model.Id);
|
||||||
|
if (existingRequest != null)
|
||||||
|
{
|
||||||
|
// check if the current user is already marked as a requester for this movie, if not, add them
|
||||||
|
//if (!existingRequest.UserHasRequested(Username))
|
||||||
|
//{
|
||||||
|
// existingRequest.RequestedUsers.Add(Username);
|
||||||
|
// await RequestService.UpdateRequestAsync(existingRequest);
|
||||||
|
//}
|
||||||
|
|
||||||
|
return new RequestEngineResult
|
||||||
|
{
|
||||||
|
RequestAdded = true,
|
||||||
|
|
||||||
|
};
|
||||||
|
//Response.AsJson(new JsonResponseModel
|
||||||
|
//{
|
||||||
|
// Result = true,
|
||||||
|
// Message =
|
||||||
|
// Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests)
|
||||||
|
// ? $"{fullMovieName} {Ombi.UI.Resources.UI.Search_SuccessfullyAdded}"
|
||||||
|
// : $"{fullMovieName} {Resources.UI.Search_AlreadyRequested}"
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
//try
|
||||||
|
//{
|
||||||
|
|
||||||
|
// var content = PlexContentRepository.GetAll();
|
||||||
|
// var movies = PlexChecker.GetPlexMovies(content);
|
||||||
|
// if (PlexChecker.IsMovieAvailable(movies.ToArray(), movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
|
||||||
|
// {
|
||||||
|
// return
|
||||||
|
// Response.AsJson(new JsonResponseModel
|
||||||
|
// {
|
||||||
|
// Result = false,
|
||||||
|
// Message = $"{fullMovieName} is already in Plex!"
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//catch (Exception e)
|
||||||
|
//{
|
||||||
|
// Log.Error(e);
|
||||||
|
// return
|
||||||
|
// Response.AsJson(new JsonResponseModel
|
||||||
|
// {
|
||||||
|
// Result = false,
|
||||||
|
// Message = string.Format(Resources.UI.Search_CouldNotCheckPlex, fullMovieName, GetMediaServerName())
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
|
||||||
|
var requestModel = new RequestModel
|
||||||
|
{
|
||||||
|
ProviderId = movieInfo.id,
|
||||||
|
Type = RequestType.Movie,
|
||||||
|
Overview = movieInfo.overview,
|
||||||
|
ImdbId = movieInfo.imdb_id,
|
||||||
|
PosterPath = movieInfo.poster_path,
|
||||||
|
Title = movieInfo.title,
|
||||||
|
ReleaseDate = !string.IsNullOrEmpty(movieInfo.release_date) ? DateTime.Parse(movieInfo.release_date) : DateTime.MinValue,
|
||||||
|
Status = movieInfo.status,
|
||||||
|
RequestedDate = DateTime.UtcNow,
|
||||||
|
Approved = false,
|
||||||
|
//RequestedUsers = new List<string> { Username },
|
||||||
|
Issues = IssueState.None,
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (ShouldAutoApprove(RequestType.Movie))
|
||||||
|
{
|
||||||
|
// model.Approved = true;
|
||||||
|
|
||||||
|
// var result = await MovieSender.Send(model);
|
||||||
|
// if (result.Result)
|
||||||
|
// {
|
||||||
|
// return await AddRequest(model, settings,
|
||||||
|
// $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
|
||||||
|
// }
|
||||||
|
// if (result.Error)
|
||||||
|
|
||||||
|
// {
|
||||||
|
// return
|
||||||
|
// Response.AsJson(new JsonResponseModel
|
||||||
|
// {
|
||||||
|
// Message = "Could not add movie, please contact your administrator",
|
||||||
|
// Result = false
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// if (!result.MovieSendingEnabled)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// return await AddRequest(model, settings, $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return Response.AsJson(new JsonResponseModel
|
||||||
|
// {
|
||||||
|
// Result = false,
|
||||||
|
// Message = Resources.UI.Search_CouchPotatoError
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return await AddRequest(requestModel, /*settings,*/
|
||||||
|
$"{fullMovieName} has been successfully added!");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
//Log.Fatal(e);
|
||||||
|
//await FaultQueue.QueueItemAsync(model, movieInfo.Id.ToString(), RequestType.Movie, FaultType.RequestFault, e.Message);
|
||||||
|
|
||||||
|
//await NotificationService.Publish(new NotificationModel
|
||||||
|
//{
|
||||||
|
// DateTime = DateTime.Now,
|
||||||
|
// User = Username,
|
||||||
|
// RequestType = RequestType.Movie,
|
||||||
|
// Title = model.Title,
|
||||||
|
// NotificationType = NotificationType.ItemAddedToFaultQueue
|
||||||
|
//});
|
||||||
|
|
||||||
|
//return Response.AsJson(new JsonResponseModel
|
||||||
|
//{
|
||||||
|
// Result = true,
|
||||||
|
// Message = $"{fullMovieName} {Resources.UI.Search_SuccessfullyAdded}"
|
||||||
|
//});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ShouldAutoApprove(RequestType requestType)
|
||||||
|
{
|
||||||
|
//var admin = Security.HasPermissions(Context.CurrentUser, Permissions.Administrator);
|
||||||
|
//// if the user is an admin, they go ahead and allow auto-approval
|
||||||
|
//if (admin) return true;
|
||||||
|
|
||||||
|
//// check by request type if the category requires approval or not
|
||||||
|
//switch (requestType)
|
||||||
|
//{
|
||||||
|
// case RequestType.Movie:
|
||||||
|
// return Security.HasPermissions(User, Permissions.AutoApproveMovie);
|
||||||
|
// case RequestType.TvShow:
|
||||||
|
// return Security.HasPermissions(User, Permissions.AutoApproveTv);
|
||||||
|
// case RequestType.Album:
|
||||||
|
// return Security.HasPermissions(User, Permissions.AutoApproveAlbum);
|
||||||
|
// default:
|
||||||
|
// return false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<RequestEngineResult> AddRequest(RequestModel model, /*PlexRequestSettings settings,*/ string message)
|
||||||
|
{
|
||||||
|
await RequestService.AddRequestAsync(model);
|
||||||
|
|
||||||
|
//if (ShouldSendNotification(model.Type, settings))
|
||||||
|
//{
|
||||||
|
// var notificationModel = new NotificationModel
|
||||||
|
// {
|
||||||
|
// Title = model.Title,
|
||||||
|
// User = Username,
|
||||||
|
// DateTime = DateTime.Now,
|
||||||
|
// NotificationType = NotificationType.NewRequest,
|
||||||
|
// RequestType = model.Type,
|
||||||
|
// ImgSrc = model.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{model.PosterPath}" : model.PosterPath
|
||||||
|
// };
|
||||||
|
// await NotificationService.Publish(notificationModel);
|
||||||
|
//}
|
||||||
|
|
||||||
|
//var limit = await RequestLimitRepo.GetAllAsync();
|
||||||
|
//var usersLimit = limit.FirstOrDefault(x => x.Username == Username && x.RequestType == model.Type);
|
||||||
|
//if (usersLimit == null)
|
||||||
|
//{
|
||||||
|
// await RequestLimitRepo.InsertAsync(new RequestLimit
|
||||||
|
// {
|
||||||
|
// Username = Username,
|
||||||
|
// RequestType = model.Type,
|
||||||
|
// FirstRequestDate = DateTime.UtcNow,
|
||||||
|
// RequestCount = 1
|
||||||
|
// });
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// usersLimit.RequestCount++;
|
||||||
|
// await RequestLimitRepo.UpdateAsync(usersLimit);
|
||||||
|
//}
|
||||||
|
|
||||||
|
return new RequestEngineResult{RequestAdded = true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
Ombi/Ombi.Core/Engine/RequestEngineResult.cs
Normal file
8
Ombi/Ombi.Core/Engine/RequestEngineResult.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ombi.Core.Engine
|
||||||
|
{
|
||||||
|
public class RequestEngineResult
|
||||||
|
{
|
||||||
|
public bool RequestAdded { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
26
Ombi/Ombi.Core/Models/Requests/IRequestService.cs
Normal file
26
Ombi/Ombi.Core/Models/Requests/IRequestService.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Requests.Models
|
||||||
|
{
|
||||||
|
public interface IRequestService
|
||||||
|
{
|
||||||
|
int AddRequest(RequestModel model);
|
||||||
|
Task<int> AddRequestAsync(RequestModel model);
|
||||||
|
void BatchDelete(IEnumerable<RequestModel> model);
|
||||||
|
void BatchUpdate(IEnumerable<RequestModel> model);
|
||||||
|
RequestModel CheckRequest(int providerId);
|
||||||
|
RequestModel CheckRequest(string musicId);
|
||||||
|
Task<RequestModel> CheckRequestAsync(int providerId);
|
||||||
|
Task<RequestModel> CheckRequestAsync(string musicId);
|
||||||
|
void DeleteRequest(RequestModel request);
|
||||||
|
Task DeleteRequestAsync(RequestModel request);
|
||||||
|
RequestModel Get(int id);
|
||||||
|
IEnumerable<RequestModel> GetAll();
|
||||||
|
Task<IEnumerable<RequestModel>> GetAllAsync();
|
||||||
|
Task<RequestModel> GetAsync(int id);
|
||||||
|
RequestBlobs UpdateRequest(RequestModel model);
|
||||||
|
}
|
||||||
|
}
|
174
Ombi/Ombi.Core/Models/Requests/JsonRequestService.cs
Normal file
174
Ombi/Ombi.Core/Models/Requests/JsonRequestService.cs
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Ombi.Core.Requests.Models;
|
||||||
|
using Ombi.Helpers;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Models.Requests
|
||||||
|
{
|
||||||
|
public class JsonRequestService : IRequestService
|
||||||
|
{
|
||||||
|
public JsonRequestService(IRequestRepository repo)
|
||||||
|
{
|
||||||
|
Repo = repo;
|
||||||
|
}
|
||||||
|
private IRequestRepository Repo { get; }
|
||||||
|
public int AddRequest(RequestModel 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(RequestModel 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 RequestModel CheckRequest(int providerId)
|
||||||
|
{
|
||||||
|
var blobs = Repo.GetAll();
|
||||||
|
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId); if (blob == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(blob.Content);
|
||||||
|
model.Id = blob.Id;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestModel> CheckRequestAsync(int providerId)
|
||||||
|
{
|
||||||
|
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
|
||||||
|
var blob = blobs.FirstOrDefault(x => x.ProviderId == providerId); if (blob == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(blob.Content);
|
||||||
|
model.Id = blob.Id;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestModel CheckRequest(string musicId)
|
||||||
|
{
|
||||||
|
var blobs = Repo.GetAll();
|
||||||
|
var blob = blobs.FirstOrDefault(x => x.MusicId == musicId); if (blob == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(blob.Content);
|
||||||
|
model.Id = blob.Id;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RequestModel> CheckRequestAsync(string musicId)
|
||||||
|
{
|
||||||
|
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
|
||||||
|
var blob = blobs.FirstOrDefault(x => x.MusicId == musicId);
|
||||||
|
|
||||||
|
if (blob == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(blob.Content);
|
||||||
|
model.Id = blob.Id;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteRequest(RequestModel request)
|
||||||
|
{
|
||||||
|
var blob = Repo.Get(request.Id);
|
||||||
|
Repo.Delete(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteRequestAsync(RequestModel request)
|
||||||
|
{
|
||||||
|
var blob = await Repo.GetAsync(request.Id).ConfigureAwait(false);
|
||||||
|
Repo.Delete(blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestBlobs UpdateRequest(RequestModel model)
|
||||||
|
{
|
||||||
|
var entity = new RequestBlobs { Type = model.Type, Content = ByteConverterHelper.ReturnBytes(model), ProviderId = model.ProviderId, Id = model.Id };
|
||||||
|
return Repo.Update(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RequestModel Get(int id)
|
||||||
|
{
|
||||||
|
var blob = Repo.Get(id);
|
||||||
|
if (blob == null)
|
||||||
|
{
|
||||||
|
return new RequestModel();
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(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<RequestModel> GetAsync(int id)
|
||||||
|
{
|
||||||
|
var blob = await Repo.GetAsync(id).ConfigureAwait(false);
|
||||||
|
if (blob == null)
|
||||||
|
{
|
||||||
|
return new RequestModel();
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(blob.Content);
|
||||||
|
model.Id = blob.Id;
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<RequestModel> GetAll()
|
||||||
|
{
|
||||||
|
var blobs = Repo.GetAll().ToList();
|
||||||
|
var retVal = new List<RequestModel>();
|
||||||
|
|
||||||
|
foreach (var b in blobs)
|
||||||
|
{
|
||||||
|
if (b == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(b.Content);
|
||||||
|
model.Id = b.Id;
|
||||||
|
retVal.Add(model);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<RequestModel>> GetAllAsync()
|
||||||
|
{
|
||||||
|
var blobs = await Repo.GetAllAsync().ConfigureAwait(false);
|
||||||
|
var retVal = new List<RequestModel>();
|
||||||
|
|
||||||
|
foreach (var b in blobs)
|
||||||
|
{
|
||||||
|
if (b == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var model = ByteConverterHelper.ReturnObject<RequestModel>(b.Content);
|
||||||
|
model.Id = b.Id;
|
||||||
|
retVal.Add(model);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BatchUpdate(IEnumerable<RequestModel> 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<RequestModel> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
Ombi/Ombi.Core/Models/Requests/RequestModel.cs
Normal file
132
Ombi/Ombi.Core/Models/Requests/RequestModel.cs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Models.Requests
|
||||||
|
{
|
||||||
|
public class RequestModel : Entity
|
||||||
|
{
|
||||||
|
public RequestModel()
|
||||||
|
{
|
||||||
|
RequestedUsers = new List<string>();
|
||||||
|
Episodes = new List<EpisodesModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ProviderId { get; set; }
|
||||||
|
public string ImdbId { get; set; }
|
||||||
|
public string TvDbId { 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 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 int[] SeasonList { get; set; }
|
||||||
|
public int SeasonCount { get; set; }
|
||||||
|
public string SeasonsRequested { get; set; }
|
||||||
|
public string MusicBrainzId { get; set; }
|
||||||
|
public List<string> RequestedUsers { get; set; }
|
||||||
|
public string ArtistName { get; set; }
|
||||||
|
public string ArtistId { get; set; }
|
||||||
|
public int IssueId { get; set; }
|
||||||
|
public List<EpisodesModel> Episodes { get; set; }
|
||||||
|
public bool Denied { get; set; }
|
||||||
|
public string DeniedReason { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// For TV Shows with a custom root folder
|
||||||
|
/// </summary>
|
||||||
|
/// <value>
|
||||||
|
/// The root folder selected.
|
||||||
|
/// </value>
|
||||||
|
public int RootFolderSelected { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<string> AllUsers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var u = new List<string>();
|
||||||
|
if (RequestedUsers != null && RequestedUsers.Any())
|
||||||
|
{
|
||||||
|
u.AddRange(RequestedUsers);
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool CanApprove => !Approved && !Available;
|
||||||
|
|
||||||
|
public string ReleaseId { get; set; }
|
||||||
|
|
||||||
|
public bool UserHasRequested(string username)
|
||||||
|
{
|
||||||
|
return AllUsers.Any(x => x.Equals(username, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static class RequestTypeDisplay
|
||||||
|
{
|
||||||
|
public static string GetString(this RequestType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case RequestType.Movie:
|
||||||
|
return "Movie";
|
||||||
|
case RequestType.TvShow:
|
||||||
|
return "TV Show";
|
||||||
|
case RequestType.Album:
|
||||||
|
return "Album";
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,9 +2,18 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard1.4</TargetFramework>
|
<TargetFramework>netstandard1.4</TargetFramework>
|
||||||
|
<!--<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>-->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
|
||||||
|
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
|
||||||
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.TheMovieDbApi.csproj" />
|
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.TheMovieDbApi.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
28
Ombi/Ombi.Helpers/ByteConverterHelper.cs
Normal file
28
Ombi/Ombi.Helpers/ByteConverterHelper.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Ombi.Helpers
|
||||||
|
{
|
||||||
|
public class ByteConverterHelper
|
||||||
|
{
|
||||||
|
public static byte[] ReturnBytes(object obj)
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(obj);
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T ReturnObject<T>(byte[] bytes)
|
||||||
|
{
|
||||||
|
var json = Encoding.UTF8.GetString(bytes);
|
||||||
|
var model = JsonConvert.DeserializeObject<T>(json);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
public static string ReturnFromBytes(byte[] bytes)
|
||||||
|
{
|
||||||
|
return Encoding.UTF8.GetString(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Ombi/Ombi.Helpers/LinqHelpers.cs
Normal file
18
Ombi/Ombi.Helpers/LinqHelpers.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ombi.Helpers
|
||||||
|
{
|
||||||
|
public static class LinqHelpers
|
||||||
|
{
|
||||||
|
public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
|
||||||
|
{
|
||||||
|
HashSet<TKey> knownKeys = new HashSet<TKey>();
|
||||||
|
foreach (TSource source1 in source)
|
||||||
|
{
|
||||||
|
if (knownKeys.Add(keySelector(source1)))
|
||||||
|
yield return source1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Ombi/Ombi.Helpers/Ombi.Helpers.csproj
Normal file
11
Ombi/Ombi.Helpers/Ombi.Helpers.csproj
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard1.4</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
15
Ombi/Ombi.Store/Context/IOmbiContext.cs
Normal file
15
Ombi/Ombi.Store/Context/IOmbiContext.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Context
|
||||||
|
{
|
||||||
|
public interface IOmbiContext : IDisposable
|
||||||
|
{
|
||||||
|
int SaveChanges();
|
||||||
|
Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||||
|
DbSet<RequestBlobs> Requests { get; set; }
|
||||||
|
}
|
||||||
|
}
|
25
Ombi/Ombi.Store/Context/OmbiContext.cs
Normal file
25
Ombi/Ombi.Store/Context/OmbiContext.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Context
|
||||||
|
{
|
||||||
|
public class OmbiContext : DbContext, IOmbiContext
|
||||||
|
{
|
||||||
|
private static bool _created = false;
|
||||||
|
public OmbiContext()
|
||||||
|
{
|
||||||
|
if(!_created)
|
||||||
|
{
|
||||||
|
_created = true;
|
||||||
|
//Database.EnsureDeleted();
|
||||||
|
Database.EnsureCreated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public DbSet<RequestBlobs> Requests { get; set; }
|
||||||
|
|
||||||
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
|
{
|
||||||
|
optionsBuilder.UseSqlite("Data Source=Ombi.db");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
Ombi/Ombi.Store/Entities/Entity.cs
Normal file
10
Ombi/Ombi.Store/Entities/Entity.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Ombi.Store.Entities
|
||||||
|
{
|
||||||
|
public abstract class Entity
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
20
Ombi/Ombi.Store/Entities/RequestBlobs.cs
Normal file
20
Ombi/Ombi.Store/Entities/RequestBlobs.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
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 string MusicId { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
public enum RequestType
|
||||||
|
{
|
||||||
|
Movie,
|
||||||
|
TvShow,
|
||||||
|
Album
|
||||||
|
}
|
||||||
|
}
|
16
Ombi/Ombi.Store/Ombi.Store.csproj
Normal file
16
Ombi/Ombi.Store/Ombi.Store.csproj
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard1.4</TargetFramework>
|
||||||
|
<!--<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>-->
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
20
Ombi/Ombi.Store/Repository/IRequestRepository.cs
Normal file
20
Ombi/Ombi.Store/Repository/IRequestRepository.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
126
Ombi/Ombi.Store/Repository/RequestJsonRepository.cs
Normal file
126
Ombi/Ombi.Store/Repository/RequestJsonRepository.cs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
|
||||||
|
return Db.Requests.Update(entity).Entity;
|
||||||
|
Db.SaveChanges();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateAll(IEnumerable<RequestBlobs> entity)
|
||||||
|
{
|
||||||
|
|
||||||
|
Db.Requests.UpdateRange(entity);
|
||||||
|
Db.SaveChanges();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void DeleteAll(IEnumerable<RequestBlobs> entity)
|
||||||
|
{
|
||||||
|
|
||||||
|
Db.Requests.RemoveRange(entity);
|
||||||
|
Db.SaveChanges();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,33 +1,16 @@
|
||||||
#region Copyright
|
using System.Threading.Tasks;
|
||||||
// /************************************************************************
|
using Ombi.Api;
|
||||||
// Copyright (c) 2017 Jamie Rees
|
using Ombi.TheMovieDbApi.Models;
|
||||||
// File: IMovieDbApi.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
|
|
||||||
namespace Ombi.TheMovieDbApi
|
namespace Ombi.TheMovieDbApi
|
||||||
{
|
{
|
||||||
public interface IMovieDbApi
|
public interface IMovieDbApi
|
||||||
{
|
{
|
||||||
|
Task<MovieResponse> GetMovieInformation(int movieId);
|
||||||
|
Task<TheMovieDbContainer<SearchResult>> NowPlaying();
|
||||||
|
Task<TheMovieDbContainer<SearchResult>> PopularMovies();
|
||||||
|
Task<TheMovieDbContainer<SearchResult>> SearchMovie(string searchTerm);
|
||||||
|
Task<TheMovieDbContainer<SearchResult>> TopRated();
|
||||||
|
Task<TheMovieDbContainer<SearchResult>> Upcoming();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard1.4</TargetFramework>
|
<TargetFramework>netstandard1.4</TargetFramework>
|
||||||
|
<!--<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>-->
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -6,7 +6,7 @@ using Ombi.TheMovieDbApi.Models;
|
||||||
|
|
||||||
namespace Ombi.TheMovieDbApi
|
namespace Ombi.TheMovieDbApi
|
||||||
{
|
{
|
||||||
public class TheMovieDbApi
|
public class TheMovieDbApi : IMovieDbApi
|
||||||
{
|
{
|
||||||
public TheMovieDbApi()
|
public TheMovieDbApi()
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,7 @@ namespace Ombi.TheMovieDbApi
|
||||||
public async Task<MovieResponse> GetMovieInformation(int movieId)
|
public async Task<MovieResponse> GetMovieInformation(int movieId)
|
||||||
{
|
{
|
||||||
var url = BaseUri.ChangePath("movie/{0}", movieId.ToString());
|
var url = BaseUri.ChangePath("movie/{0}", movieId.ToString());
|
||||||
AddHeaders(url);
|
url = AddHeaders(url);
|
||||||
return await Api.Get<MovieResponse>(url);
|
return await Api.Get<MovieResponse>(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio 15
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 15.0.26228.9
|
VisualStudioVersion = 15.0.26228.10
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj", "{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj", "{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}"
|
||||||
EndProject
|
EndProject
|
||||||
|
@ -20,6 +20,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.TheMovieDbApi", "Ombi.
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api", "Ombi.Api\Ombi.Api.csproj", "{EA31F915-31F9-4318-B521-1500CDF40DDF}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api", "Ombi.Api\Ombi.Api.csproj", "{EA31F915-31F9-4318-B521-1500CDF40DDF}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers", "Ombi.Helpers\Ombi.Helpers.csproj", "{C182B435-1FAB-49C5-9BF9-F7F5C6EF3C94}"
|
||||||
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Store", "Ombi.Store\Ombi.Store.csproj", "{68086581-1EFD-4390-8100-47F87D1CB628}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -42,6 +46,14 @@ Global
|
||||||
{EA31F915-31F9-4318-B521-1500CDF40DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{EA31F915-31F9-4318-B521-1500CDF40DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{EA31F915-31F9-4318-B521-1500CDF40DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{EA31F915-31F9-4318-B521-1500CDF40DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{EA31F915-31F9-4318-B521-1500CDF40DDF}.Release|Any CPU.Build.0 = Release|Any CPU
|
{EA31F915-31F9-4318-B521-1500CDF40DDF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C182B435-1FAB-49C5-9BF9-F7F5C6EF3C94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C182B435-1FAB-49C5-9BF9-F7F5C6EF3C94}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C182B435-1FAB-49C5-9BF9-F7F5C6EF3C94}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C182B435-1FAB-49C5-9BF9-F7F5C6EF3C94}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{68086581-1EFD-4390-8100-47F87D1CB628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{68086581-1EFD-4390-8100-47F87D1CB628}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{68086581-1EFD-4390-8100-47F87D1CB628}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{68086581-1EFD-4390-8100-47F87D1CB628}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
23
Ombi/Ombi/Controllers/RequestController.cs
Normal file
23
Ombi/Ombi/Controllers/RequestController.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Ombi.Core.Engine;
|
||||||
|
using Ombi.Core.Models.Search;
|
||||||
|
|
||||||
|
namespace Ombi.Controllers
|
||||||
|
{
|
||||||
|
public class RequestController : BaseApiController
|
||||||
|
{
|
||||||
|
public RequestController(IRequestEngine engine)
|
||||||
|
{
|
||||||
|
RequestEngine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IRequestEngine RequestEngine { get; }
|
||||||
|
|
||||||
|
[HttpPost("movie")]
|
||||||
|
public async Task<RequestEngineResult> SearchMovie([FromBody]SearchMovieViewModel movie)
|
||||||
|
{
|
||||||
|
return await RequestEngine.RequestMovie(movie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,8 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.0" />
|
<PackageReference Include="Microsoft.VisualStudio.Web.BrowserLink" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -7,11 +7,18 @@ using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Core;
|
using Ombi.Core;
|
||||||
|
using Ombi.Core.Engine;
|
||||||
|
using Ombi.Core.Models.Requests;
|
||||||
|
using Ombi.Core.Requests.Models;
|
||||||
|
using Ombi.Store.Context;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
using Ombi.TheMovieDbApi;
|
||||||
|
|
||||||
namespace Ombi
|
namespace Ombi
|
||||||
{
|
{
|
||||||
|
@ -25,6 +32,13 @@ namespace Ombi
|
||||||
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
|
||||||
.AddEnvironmentVariables();
|
.AddEnvironmentVariables();
|
||||||
Configuration = builder.Build();
|
Configuration = builder.Build();
|
||||||
|
|
||||||
|
using (var ctx = new OmbiContext())
|
||||||
|
{
|
||||||
|
ctx.Database.EnsureCreated();
|
||||||
|
ctx.Database.Migrate();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IConfigurationRoot Configuration { get; }
|
public IConfigurationRoot Configuration { get; }
|
||||||
|
@ -34,7 +48,13 @@ namespace Ombi
|
||||||
{
|
{
|
||||||
// Add framework services.
|
// Add framework services.
|
||||||
services.AddMvc();
|
services.AddMvc();
|
||||||
|
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
|
||||||
services.AddTransient<IMovieEngine, MovieEngine>();
|
services.AddTransient<IMovieEngine, MovieEngine>();
|
||||||
|
services.AddTransient<IRequestEngine, RequestEngine>();
|
||||||
|
services.AddTransient<IMovieDbApi, TheMovieDbApi.TheMovieDbApi>();
|
||||||
|
services.AddTransient<IRequestService, JsonRequestService>();
|
||||||
|
services.AddTransient<IOmbiContext, OmbiContext>();
|
||||||
|
services.AddTransient<IRequestRepository, RequestJsonRepository>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { PageNotFoundComponent } from './errors/not-found.component';
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
import { SearchService } from './services/search.service';
|
import { SearchService } from './services/search.service';
|
||||||
|
import { RequestService } from './services/request.service';
|
||||||
|
|
||||||
import { ButtonModule } from 'primeng/primeng';
|
import { ButtonModule } from 'primeng/primeng';
|
||||||
import { MenubarModule } from 'primeng/components/menubar/menubar';
|
import { MenubarModule } from 'primeng/components/menubar/menubar';
|
||||||
|
@ -40,7 +41,8 @@ const routes: Routes = [
|
||||||
SearchComponent
|
SearchComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService
|
SearchService,
|
||||||
|
RequestService
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
|
4
Ombi/Ombi/app/interfaces/IRequestEngineResult.ts
Normal file
4
Ombi/Ombi/app/interfaces/IRequestEngineResult.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IRequestEngineResult {
|
||||||
|
requestAdded: boolean,
|
||||||
|
message: string
|
||||||
|
}
|
|
@ -5,22 +5,25 @@ import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
import 'rxjs/add/operator/map';
|
import 'rxjs/add/operator/map';
|
||||||
|
|
||||||
import { SearchService } from '../services/search.service';
|
import { SearchService } from '../services/search.service';
|
||||||
|
import { RequestService } from '../services/request.service';
|
||||||
|
|
||||||
import { ISearchMovieResult } from './interfaces/ISearchMovieResult';
|
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
|
||||||
|
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ombi',
|
selector: 'ombi',
|
||||||
moduleId: module.id,
|
moduleId: module.id,
|
||||||
templateUrl: './search.component.html',
|
templateUrl: './search.component.html',
|
||||||
providers: [SearchService]
|
providers: [SearchService, RequestService]
|
||||||
})
|
})
|
||||||
export class SearchComponent implements OnInit {
|
export class SearchComponent implements OnInit {
|
||||||
|
|
||||||
searchText: string;
|
searchText: string;
|
||||||
searchChanged: Subject<string> = new Subject<string>();
|
searchChanged: Subject<string> = new Subject<string>();
|
||||||
movieResults: ISearchMovieResult[];
|
movieResults: ISearchMovieResult[];
|
||||||
|
result: IRequestEngineResult;
|
||||||
|
|
||||||
constructor(private searchService: SearchService) {
|
constructor(private searchService: SearchService, private requestService: RequestService) {
|
||||||
this.searchChanged
|
this.searchChanged
|
||||||
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
|
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
|
||||||
.distinctUntilChanged() // only emit if value is different from previous value
|
.distinctUntilChanged() // only emit if value is different from previous value
|
||||||
|
@ -37,6 +40,10 @@ export class SearchComponent implements OnInit {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.searchText = "";
|
this.searchText = "";
|
||||||
this.movieResults = [];
|
this.movieResults = [];
|
||||||
|
this.result = {
|
||||||
|
message: "",
|
||||||
|
requestAdded:false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
search(text: any) {
|
search(text: any) {
|
||||||
|
@ -44,7 +51,7 @@ export class SearchComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
request(searchResult: ISearchMovieResult) {
|
request(searchResult: ISearchMovieResult) {
|
||||||
console.log(searchResult);
|
this.requestService.requestMovie(searchResult).subscribe(x => this.result = x);
|
||||||
}
|
}
|
||||||
|
|
||||||
popularMovies() {
|
popularMovies() {
|
||||||
|
|
18
Ombi/Ombi/app/services/request.service.ts
Normal file
18
Ombi/Ombi/app/services/request.service.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Http } from '@angular/http';
|
||||||
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
|
||||||
|
import { ServiceHelpers } from './service.helpers';
|
||||||
|
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
|
||||||
|
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RequestService {
|
||||||
|
constructor(private http: Http) {
|
||||||
|
}
|
||||||
|
|
||||||
|
requestMovie(movie: ISearchMovieResult): Observable<IRequestEngineResult> {
|
||||||
|
return this.http.post('/api/Request/Movie/', JSON.stringify(movie), ServiceHelpers.RequestOptions).map(ServiceHelpers.extractData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ import { Http } from '@angular/http';
|
||||||
import { Observable } from 'rxjs/Rx';
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
|
||||||
import { ServiceHelpers } from './service.helpers';
|
import { ServiceHelpers } from './service.helpers';
|
||||||
import { ISearchMovieResult } from '../search/interfaces/ISearchMovieResult';
|
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService {
|
export class SearchService {
|
||||||
|
|
|
@ -21,10 +21,10 @@ before_build:
|
||||||
build_script:
|
build_script:
|
||||||
- dotnet build
|
- dotnet build
|
||||||
after_build:
|
after_build:
|
||||||
- dotnet publish -c Release -r win10-x64
|
- dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64
|
||||||
- dotnet publish -c Release -r osx.10.12-x64
|
- dotnet publish -c Release /p:AppRuntimeIdentifier=osx.10.12-x64
|
||||||
- dotnet publish -c Release -r ubuntu.16.10-x64
|
- dotnet publish -c Release /p:AppRuntimeIdentifier=ubuntu.16.10-x64
|
||||||
- dotnet publish -c Release -r debian.8-x64
|
- dotnet publish -c Release /p:AppRuntimeIdentifier=debian.8-x64
|
||||||
- cmd: >-
|
- cmd: >-
|
||||||
7z a Ombi_windows.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\win10-x64\publish
|
7z a Ombi_windows.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\win10-x64\publish
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue