Merge branch 'DotNetCore' into dev

pull/1350/head
Jamie 7 years ago committed by GitHub
commit 90827aa7f9

16
Ombi/.gitignore vendored

@ -0,0 +1,16 @@
../app/**/*.js
../app/**/*.js.map
../wwwroot/**
# dependencies
../node_modules
../bower_components
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings

@ -0,0 +1,6 @@
;https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/
cd ..
dotnet restore
dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64
exit

@ -0,0 +1,9 @@
;https://docs.microsoft.com/en-us/dotnet/articles/core/deploying/
cd ..
dotnet restore
dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64
dotnet publish -c Release /p:AppRuntimeIdentifier=osx.10.12-x64
dotnet publish -c Release /p:AppRuntimeIdentifier=ubuntu.16.10-x64
dotnet publish -c Release /p:AppRuntimeIdentifier=debian.8-x64
exit

@ -0,0 +1,29 @@
using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Ombi.Api
{
public class Api
{
public static JsonSerializerSettings Settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
public async Task<T> Get<T>(Uri uri)
{
var h = new HttpClient();
var response = await h.GetAsync(uri);
if (!response.IsSuccessStatusCode)
{
// Logging
}
var receiveString = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(receiveString, Settings);
}
}
}

@ -0,0 +1,78 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: ApiHelper.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
namespace Ombi.Api
{
public static class ApiHelper
{
public static Uri ChangePath(this Uri uri, string path, params string[] args)
{
var builder = new UriBuilder(uri);
if (args != null && args.Length > 0)
{
builder.Path = builder.Path + string.Format(path, args);
}
else
{
builder.Path += path;
}
return builder.Uri;
}
public static Uri ChangePath(this Uri uri, string path)
{
return ChangePath(uri, path, null);
}
public static Uri AddQueryParameter(this Uri uri, string parameter, string value)
{
if (string.IsNullOrEmpty(parameter) || string.IsNullOrEmpty(value)) return uri;
var builder = new UriBuilder(uri);
var startingTag = string.Empty;
var hasQuery = false;
if (string.IsNullOrEmpty(builder.Query))
{
startingTag = "?";
}
else
{
hasQuery = true;
startingTag = builder.Query.Contains("?") ? "&" : "?";
}
builder.Query = hasQuery
? $"{builder.Query}{startingTag}{parameter}={value}"
: $"{startingTag}{parameter}={value}";
return builder.Uri;
}
}
}

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<!--<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>-->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="10.0.2" />
</ItemGroup>
</Project>

@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Core.Models.Search;
namespace Ombi.Core
{
public interface IMovieEngine
{
Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies();
Task<IEnumerable<SearchMovieViewModel>> PopularMovies();
Task<IEnumerable<SearchMovieViewModel>> ProcessMovieSearch(string search);
Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies();
Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies();
}
}

@ -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);
}
}

@ -0,0 +1,191 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Core.Models.Requests;
using Ombi.Core.Models.Search;
using Ombi.Core.Requests.Models;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.TheMovieDbApi.Models;
namespace Ombi.Core.Engine
{
public class MovieEngine : IMovieEngine
{
public MovieEngine(IRequestService service)
{
RequestService = service;
}
private IRequestService RequestService { get; }
public async Task<IEnumerable<SearchMovieViewModel>> ProcessMovieSearch(string search)
{
var api = new TheMovieDbApi.TheMovieDbApi();
var result = await api.SearchMovie(search);
if (result != null)
{
return await TransformMovieResultsToResponse(result.results);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> PopularMovies()
{
var api = new TheMovieDbApi.TheMovieDbApi();
var result = await api.PopularMovies();
if (result != null)
{
return await TransformMovieResultsToResponse(result.results);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{
var api = new TheMovieDbApi.TheMovieDbApi();
var result = await api.TopRated();
if (result != null)
{
return await TransformMovieResultsToResponse(result.results);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{
var api = new TheMovieDbApi.TheMovieDbApi();
var result = await api.Upcoming();
if (result != null)
{
return await TransformMovieResultsToResponse(result.results);
}
return null;
}
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{
var api = new TheMovieDbApi.TheMovieDbApi();
var result = await api.NowPlaying();
if (result != null)
{
return await TransformMovieResultsToResponse(result.results);
}
return null;
}
private async Task<List<SearchMovieViewModel>> TransformMovieResultsToResponse(IEnumerable<SearchResult> movies)
{
await Task.Yield();
var viewMovies = new List<SearchMovieViewModel>();
//var counter = 0;
Dictionary<int, RequestModel> dbMovies = await RequestedMovies();
foreach (var movie in movies)
{
var viewMovie = new SearchMovieViewModel
{
Adult = movie.adult,
BackdropPath = movie.backdrop_path,
Id = movie.id,
OriginalLanguage = movie.original_language,
OriginalTitle = movie.original_title,
Overview = movie.overview,
Popularity = movie.popularity,
PosterPath = movie.poster_path,
ReleaseDate = string.IsNullOrEmpty(movie.release_date) ? DateTime.MinValue : DateTime.Parse(movie.release_date),
Title = movie.title,
Video = movie.video,
VoteAverage = movie.vote_average,
VoteCount = movie.vote_count
};
viewMovies.Add(viewMovie);
//if (counter <= 5) // Let's only do it for the first 5 items
//{
// var movieInfo = MovieApi.GetMovieInformationWithVideos(movie.Id);
// // TODO needs to be careful about this, it's adding extra time to search...
// // https://www.themoviedb.org/talk/5807f4cdc3a36812160041f2
// viewMovie.ImdbId = movieInfo?.imdb_id;
// viewMovie.Homepage = movieInfo?.homepage;
// var videoId = movieInfo?.video ?? false
// ? movieInfo?.videos?.results?.FirstOrDefault()?.key
// : string.Empty;
// viewMovie.Trailer = string.IsNullOrEmpty(videoId)
// ? string.Empty
// : $"https://www.youtube.com/watch?v={videoId}";
// counter++;
//}
// var canSee = CanUserSeeThisRequest(viewMovie.Id, Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests), dbMovies);
// var plexSettings = await PlexService.GetSettingsAsync();
// var embySettings = await EmbySettings.GetSettingsAsync();
// if (plexSettings.Enable)
// {
// var content = PlexContentRepository.GetAll();
// var plexMovies = PlexChecker.GetPlexMovies(content);
// var plexMovie = PlexChecker.GetMovie(plexMovies.ToArray(), movie.Title,
// movie.ReleaseDate?.Year.ToString(),
// viewMovie.ImdbId);
// if (plexMovie != null)
// {
// viewMovie.Available = true;
// viewMovie.PlexUrl = plexMovie.Url;
// }
// }
// if (embySettings.Enable)
// {
// var embyContent = EmbyContentRepository.GetAll();
// var embyMovies = EmbyChecker.GetEmbyMovies(embyContent);
// var embyMovie = EmbyChecker.GetMovie(embyMovies.ToArray(), movie.Title,
// movie.ReleaseDate?.Year.ToString(), viewMovie.ImdbId);
// if (embyMovie != null)
// {
// viewMovie.Available = true;
// }
// }
if (dbMovies.ContainsKey(movie.id) /*&& canSee*/) // compare to the requests db
{
var dbm = dbMovies[movie.id];
viewMovie.Requested = true;
viewMovie.Approved = dbm.Approved;
viewMovie.Available = dbm.Available;
}
// else if (canSee)
// {
// bool exists = IsMovieInCache(movie, viewMovie.ImdbId);
// viewMovie.Approved = exists;
// viewMovie.Requested = exists;
// }
// viewMovies.Add(viewMovie);
//}
}
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;
}
}
}

@ -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};
}
}
}

@ -0,0 +1,8 @@
namespace Ombi.Core.Engine
{
public class RequestEngineResult
{
public bool RequestAdded { get; set; }
public string Message { get; set; }
}
}

@ -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);
}
}

@ -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);
}
}
}

@ -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;
}
}
}

@ -0,0 +1,54 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: SearchMovieViewModel.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
namespace Ombi.Core.Models.Search
{
public class SearchMovieViewModel : SearchViewModel
{
public bool Adult { get; set; }
public string BackdropPath { get; set; }
public List<int> GenreIds { get; set; }
public int Id { get; set; }
public string OriginalLanguage { get; set; }
public string OriginalTitle { get; set; }
public string Overview { get; set; }
public double Popularity { get; set; }
public string PosterPath { get; set; }
public DateTime? ReleaseDate { get; set; }
public string Title { get; set; }
public bool Video { get; set; }
public double VoteAverage { get; set; }
public int VoteCount { get; set; }
public bool AlreadyInCp { get; set; }
public string Trailer { get; set; }
public string Homepage { get; set; }
public string ImdbId { get; set; }
}
}

@ -0,0 +1,36 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: SearchViewModel.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.Core.Models.Search
{
public class SearchViewModel
{
public bool Approved { get; set; }
public bool Requested { get; set; }
public bool Available { get; set; }
public string PlexUrl { get; set; }
}
}

@ -0,0 +1,20 @@
<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>
<ProjectReference Include="..\Ombi.Helpers\Ombi.Helpers.csproj" />
<ProjectReference Include="..\Ombi.Store\Ombi.Store.csproj" />
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.TheMovieDbApi.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,14 @@
using System.Threading.Tasks;
namespace Ombi.Core.Settings
{
public interface ISettingsService<T>
{
T GetSettings();
Task<T> GetSettingsAsync();
bool SaveSettings(T model);
Task<bool> SaveSettingsAsync(T model);
bool Delete(T model);
Task<bool> DeleteAsync(T model);
}
}

@ -0,0 +1,7 @@
namespace Ombi.Core.Settings.Models
{
public class Settings
{
public int Id { get; set; }
}
}

@ -0,0 +1,129 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
namespace Ombi.Core.Settings
{
public class SettingsServiceV2<T> : ISettingsService<T>
where T : Models.Settings, new()
{
public SettingsServiceV2(ISettingsRepository repo)
{
Repo = repo;
EntityName = typeof(T).Name;
}
private ISettingsRepository Repo { get; }
private string EntityName { get; }
public T GetSettings()
{
var result = Repo.Get(EntityName);
if (result == null)
{
return new T();
}
result.Content = DecryptSettings(result);
var obj = string.IsNullOrEmpty(result.Content) ? null : JsonConvert.DeserializeObject<T>(result.Content, SerializerSettings.Settings);
var model = obj;
return model;
}
public async Task<T> GetSettingsAsync()
{
var result = await Repo.GetAsync(EntityName).ConfigureAwait(false);
if (result == null)
{
return new T();
}
result.Content = DecryptSettings(result);
return string.IsNullOrEmpty(result.Content) ? null : JsonConvert.DeserializeObject<T>(result.Content, SerializerSettings.Settings);
}
public bool SaveSettings(T model)
{
var entity = Repo.Get(EntityName);
if (entity == null)
{
var newEntity = model;
var settings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(newEntity, SerializerSettings.Settings) };
settings.Content = EncryptSettings(settings);
var insertResult = Repo.Insert(settings);
return insertResult != null;
}
var modified = model;
modified.Id = entity.Id;
var globalSettings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(modified, SerializerSettings.Settings), Id = entity.Id };
globalSettings.Content = EncryptSettings(globalSettings);
Repo.Update(globalSettings);
return true;
}
public async Task<bool> SaveSettingsAsync(T model)
{
var entity = await Repo.GetAsync(EntityName);
if (entity == null)
{
var newEntity = model;
var settings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(newEntity, SerializerSettings.Settings) };
settings.Content = EncryptSettings(settings);
var insertResult = await Repo.InsertAsync(settings).ConfigureAwait(false);
return insertResult != null;
}
var modified = model;
modified.Id = entity.Id;
var globalSettings = new GlobalSettings { SettingsName = EntityName, Content = JsonConvert.SerializeObject(modified, SerializerSettings.Settings), Id = entity.Id };
globalSettings.Content = EncryptSettings(globalSettings);
await Repo.UpdateAsync(globalSettings).ConfigureAwait(false);
return true;
}
public void Delete(T model)
{
var entity = Repo.Get(EntityName);
if (entity != null)
{
Repo.Delete(entity);
}
}
public async Task DeleteAsync(T model)
{
var entity = Repo.Get(EntityName);
if (entity != null)
{
await Repo.DeleteAsync(entity);
}
}
private string EncryptSettings(GlobalSettings settings)
{
return StringCipher.Encrypt(settings.Content, settings.SettingsName);
}
private string DecryptSettings(GlobalSettings settings)
{
return StringCipher.Decrypt(settings.Content, settings.SettingsName);
}
}
}

@ -0,0 +1,57 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Ombi.Core;
using Ombi.Core.Engine;
using Ombi.Core.Models.Requests;
using Ombi.Core.Requests.Models;
using Ombi.Core.Settings;
using Ombi.Store.Context;
using Ombi.Store.Repository;
using Ombi.TheMovieDbApi;
namespace Ombi.DependencyInjection
{
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
public static class IocExtensions
{
public static IServiceCollection RegisterDependencies(this IServiceCollection services)
{
services.RegisterEngines();
services.RegisterApi();
services.RegisterServices();
services.RegisterStore();
return services;
}
public static IServiceCollection RegisterEngines(this IServiceCollection services)
{
services.AddTransient<IMovieEngine, MovieEngine>();
services.AddTransient<IRequestEngine, RequestEngine>();
return services;
}
public static IServiceCollection RegisterApi(this IServiceCollection services)
{
services.AddTransient<IMovieDbApi, TheMovieDbApi.TheMovieDbApi>();
return services;
}
public static IServiceCollection RegisterStore(this IServiceCollection services)
{
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
services.AddTransient<IOmbiContext, OmbiContext>();
services.AddTransient<IRequestRepository, RequestJsonRepository>();
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsServiceV2<>));
return services;
}
public static IServiceCollection RegisterServices(this IServiceCollection services)
{
services.AddTransient<IRequestService, JsonRequestService>();
return services;
}
}
}

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Extensions.DependencyInjection.Abstractions">
<HintPath>..\..\..\..\..\.nuget\packages\microsoft.extensions.dependencyinjection.abstractions\1.1.0\lib\netstandard1.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

@ -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);
}
}
}

@ -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;
}
}
}
}

@ -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>

@ -0,0 +1,15 @@
using Newtonsoft.Json;
namespace Ombi.Helpers
{
public static class SerializerSettings
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
Formatting = Formatting.None,
//TypeNameHandling = TypeNameHandling.Objects,
//TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
NullValueHandling = NullValueHandling.Ignore
};
}
}

@ -0,0 +1,120 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace Ombi.Helpers
{
public class StringCipher
{
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int Keysize = 256;
/// <summary>
/// Decrypts the specified cipher text.
/// </summary>
/// <param name="cipherText">The cipher text.</param>
/// <param name="passPhrase">The pass phrase.</param>
/// <returns></returns>
public static string Decrypt(string cipherText, string passPhrase)
{
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
var aes = Aes.Create();
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
/// <summary>
/// Encrypts the specified plain text.
/// </summary>
/// <param name="plainText">The plain text.</param>
/// <param name="passPhrase">The pass phrase.</param>
/// <returns></returns>
public static string Encrypt(string plainText, string passPhrase)
{
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
var saltStringBytes = Generate256BitsOfRandomEntropy();
var ivStringBytes = Generate256BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 256;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
/// <summary>
/// Generate256s the bits of random entropy.
/// </summary>
/// <returns></returns>
private static byte[] Generate256BitsOfRandomEntropy()
{
var randomBytes = new byte[32]; // 32 Bytes will give us 256 bits.
using (var rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
}

@ -0,0 +1,16 @@
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; }
DbSet<GlobalSettings> Settings { get; set; }
}
}

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
using Ombi.Store.Entities;
namespace Ombi.Store.Context
{
public class OmbiContext : DbContext, IOmbiContext
{
private static bool _created;
public OmbiContext()
{
if (_created) return;
_created = true;
Database.EnsureCreated();
Database.Migrate();
}
public DbSet<RequestBlobs> Requests { get; set; }
public DbSet<GlobalSettings> Settings { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=Ombi.db");
}
}
}

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations;
namespace Ombi.Store.Entities
{
public abstract class Entity
{
[Key]
public int Id { get; set; }
}
}

@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Ombi.Store.Entities
{
[Table("GlobalSettings")]
public class GlobalSettings : Entity
{
public string Content { get; set; }
public string SettingsName { get; set; }
}
}

@ -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
}
}

@ -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>

@ -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);
}
}

@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Store.Entities;
namespace Ombi.Store.Repository
{
public interface ISettingsRepository
{
/// <summary>
/// Inserts the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
GlobalSettings Insert(GlobalSettings entity);
Task<GlobalSettings> InsertAsync(GlobalSettings entity);
/// <summary>
/// Gets all.
/// </summary>
/// <returns></returns>
IEnumerable<GlobalSettings> GetAll();
Task<IEnumerable<GlobalSettings>> GetAllAsync();
/// <summary>
/// Gets the specified identifier.
/// </summary>
/// <param name="settingsName">Name of the settings.</param>
/// <returns></returns>
GlobalSettings Get(string settingsName);
Task<GlobalSettings> GetAsync(string settingsName);
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
Task DeleteAsync(GlobalSettings entity);
void Delete(GlobalSettings entity);
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
Task UpdateAsync(GlobalSettings entity);
void Update(GlobalSettings entity);
}
}

@ -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();
}
}
}

@ -0,0 +1,84 @@
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 SettingsJsonRepository : ISettingsRepository
{
public SettingsJsonRepository(IOmbiContext ctx)
{
Db = ctx;
}
private IOmbiContext Db { get; }
public GlobalSettings Insert(GlobalSettings entity)
{
var settings = Db.Settings.Add(entity);
Db.SaveChanges();
return settings.Entity;
}
public async Task<GlobalSettings> InsertAsync(GlobalSettings entity)
{
var settings = await Db.Settings.AddAsync(entity).ConfigureAwait(false);
await Db.SaveChangesAsync().ConfigureAwait(false);
return settings.Entity;
}
public IEnumerable<GlobalSettings> GetAll()
{
var page = Db.Settings.ToList();
return page;
}
public async Task<IEnumerable<GlobalSettings>> GetAllAsync()
{
var page = await Db.Settings.ToListAsync();
return page;
}
public GlobalSettings Get(string pageName)
{
return Db.Settings.FirstOrDefault(x => x.SettingsName == pageName);
}
public async Task<GlobalSettings> GetAsync(string settingsName)
{
return await Db.Settings.FirstOrDefaultAsync(x => x.SettingsName == settingsName);
}
public async Task DeleteAsync(GlobalSettings entity)
{
Db.Settings.Remove(entity);
await Db.SaveChangesAsync();
}
public async Task UpdateAsync(GlobalSettings entity)
{
Db.Settings.Update(entity);
await Db.SaveChangesAsync();
}
public void Delete(GlobalSettings entity)
{
Db.Settings.Remove(entity);
Db.SaveChanges();
}
public void Update(GlobalSettings entity)
{
Db.Settings.Update(entity);
Db.SaveChanges();
}
}
}

@ -0,0 +1,16 @@
using System.Threading.Tasks;
using Ombi.Api;
using Ombi.TheMovieDbApi.Models;
namespace Ombi.TheMovieDbApi
{
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();
}
}

@ -0,0 +1,10 @@
namespace Ombi.TheMovieDbApi.Models
{
public class BelongsToCollection
{
public int id { get; set; }
public string name { get; set; }
public string poster_path { get; set; }
public string backdrop_path { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.TheMovieDbApi.Models
{
public class Genre
{
public int id { get; set; }
public string name { get; set; }
}
}

@ -0,0 +1,58 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: MovieResponse.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.Models
{
public class MovieResponse
{
public bool adult { get; set; }
public string backdrop_path { get; set; }
public BelongsToCollection belongs_to_collection { get; set; }
public int budget { get; set; }
public Genre[] genres { get; set; }
public string homepage { get; set; }
public int id { get; set; }
public string imdb_id { get; set; }
public string original_language { get; set; }
public string original_title { get; set; }
public string overview { get; set; }
public float popularity { get; set; }
public string poster_path { get; set; }
public ProductionCompanies[] production_companies { get; set; }
public ProductionCountries[] production_countries { get; set; }
public string release_date { get; set; }
public int revenue { get; set; }
public int runtime { get; set; }
public SpokenLanguages[] spoken_languages { get; set; }
public string status { get; set; }
public string tagline { get; set; }
public string title { get; set; }
public bool video { get; set; }
public float vote_average { get; set; }
public int vote_count { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.TheMovieDbApi.Models
{
public class ProductionCompanies
{
public string name { get; set; }
public int id { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.TheMovieDbApi.Models
{
public class ProductionCountries
{
public string iso_3166_1 { get; set; }
public string name { get; set; }
}
}

@ -0,0 +1,46 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: SearchResult.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.Models
{
public class SearchResult
{
public string poster_path { get; set; }
public bool adult { get; set; }
public string overview { get; set; }
public string release_date { get; set; }
public int?[] genre_ids { get; set; }
public int id { get; set; }
public string original_title { get; set; }
public string original_language { get; set; }
public string title { get; set; }
public string backdrop_path { get; set; }
public float popularity { get; set; }
public int vote_count { get; set; }
public bool video { get; set; }
public float vote_average { get; set; }
}
}

@ -0,0 +1,8 @@
namespace Ombi.TheMovieDbApi.Models
{
public class SpokenLanguages
{
public string iso_639_1 { get; set; }
public string name { get; set; }
}
}

@ -0,0 +1,39 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: TheMovieDbContainer.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
namespace Ombi.TheMovieDbApi.Models
{
public class TheMovieDbContainer<T>
{
public int page { get; set; }
public List<T> results { get; set; }
public int total_results { get; set; }
public int total_pages { get; set; }
}
}

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<!--<NetStandardImplicitPackageVersion>1.6.1</NetStandardImplicitPackageVersion>-->
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api;
using Ombi.TheMovieDbApi.Models;
namespace Ombi.TheMovieDbApi
{
public class TheMovieDbApi : IMovieDbApi
{
public TheMovieDbApi()
{
Api = new Api.Api();
}
private const string ApiToken = "b8eabaf5608b88d0298aa189dd90bf00";
private static readonly Uri BaseUri = new Uri("http://api.themoviedb.org/3/");
public Api.Api Api { get; }
public async Task<MovieResponse> GetMovieInformation(int movieId)
{
var url = BaseUri.ChangePath("movie/{0}", movieId.ToString());
url = AddHeaders(url);
return await Api.Get<MovieResponse>(url);
}
public async Task<TheMovieDbContainer<SearchResult>> SearchMovie(string searchTerm)
{
var url = BaseUri.ChangePath("search/movie/");
url = AddHeaders(url);
url = url.AddQueryParameter("query", searchTerm);
return await Api.Get<TheMovieDbContainer<SearchResult>>(url);
}
public async Task<TheMovieDbContainer<SearchResult>> PopularMovies()
{
var url = BaseUri.ChangePath("movie/popular");
url = AddHeaders(url);
return await Api.Get<TheMovieDbContainer<SearchResult>>(url);
}
public async Task<TheMovieDbContainer<SearchResult>> TopRated()
{
var url = BaseUri.ChangePath("movie/top_rated");
url = AddHeaders(url);
return await Api.Get<TheMovieDbContainer<SearchResult>>(url);
}
public async Task<TheMovieDbContainer<SearchResult>> Upcoming()
{
var url = BaseUri.ChangePath("movie/upcoming");
url = AddHeaders(url);
return await Api.Get<TheMovieDbContainer<SearchResult>>(url);
}
public async Task<TheMovieDbContainer<SearchResult>> NowPlaying()
{
var url = BaseUri.ChangePath("movie/now_playing");
url = AddHeaders(url);
return await Api.Get<TheMovieDbContainer<SearchResult>>(url);
}
private Uri AddHeaders(Uri url)
{
return url.AddQueryParameter("api_key", ApiToken);
}
}
}

@ -0,0 +1,71 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.10
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi", "Ombi\Ombi.csproj", "{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9D30CCF8-A115-4EB7-A34D-07780D752789}"
ProjectSection(SolutionItems) = preProject
..\appveyor.yml = ..\appveyor.yml
Build\publish windows.bat = Build\publish windows.bat
Build\publish.bat = Build\publish.bat
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Core", "Ombi.Core\Ombi.Core.csproj", "{F56E79C7-791D-4668-A0EC-29E3BBC8D24B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Api", "Api", "{9293CA11-360A-4C20-A674-B9E794431BF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.TheMovieDbApi", "Ombi.TheMovieDbApi\Ombi.TheMovieDbApi.csproj", "{132DA282-5894-4570-8916-D8C18ED2CE84}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Api", "Ombi.Api\Ombi.Api.csproj", "{EA31F915-31F9-4318-B521-1500CDF40DDF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers", "Ombi.Helpers\Ombi.Helpers.csproj", "{C182B435-1FAB-49C5-9BF9-F7F5C6EF3C94}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Store", "Ombi.Store\Ombi.Store.csproj", "{68086581-1EFD-4390-8100-47F87D1CB628}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.DependencyInjection", "Ombi.DependencyInjection\Ombi.DependencyInjection.csproj", "{B39E4558-C557-48E7-AA74-19C5CD809617}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C987AA67-AFE1-468F-ACD3-EAD5A48E1F6A}.Release|Any CPU.Build.0 = Release|Any CPU
{F56E79C7-791D-4668-A0EC-29E3BBC8D24B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F56E79C7-791D-4668-A0EC-29E3BBC8D24B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F56E79C7-791D-4668-A0EC-29E3BBC8D24B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F56E79C7-791D-4668-A0EC-29E3BBC8D24B}.Release|Any CPU.Build.0 = Release|Any CPU
{132DA282-5894-4570-8916-D8C18ED2CE84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{132DA282-5894-4570-8916-D8C18ED2CE84}.Debug|Any CPU.Build.0 = Debug|Any CPU
{132DA282-5894-4570-8916-D8C18ED2CE84}.Release|Any CPU.ActiveCfg = Release|Any CPU
{132DA282-5894-4570-8916-D8C18ED2CE84}.Release|Any CPU.Build.0 = Release|Any CPU
{EA31F915-31F9-4318-B521-1500CDF40DDF}.Debug|Any CPU.ActiveCfg = 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.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
{B39E4558-C557-48E7-AA74-19C5CD809617}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B39E4558-C557-48E7-AA74-19C5CD809617}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B39E4558-C557-48E7-AA74-19C5CD809617}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{132DA282-5894-4570-8916-D8C18ED2CE84} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{EA31F915-31F9-4318-B521-1500CDF40DDF} = {9293CA11-360A-4C20-A674-B9E794431BF5}
EndGlobalSection
EndGlobal

@ -0,0 +1,17 @@
/app/**/*.js
/app/**/*.js.map
/wwwroot/**
# dependencies
/node_modules
/bower_components
# misc
/.sass-cache
/connect.lock
/coverage/*
/libpeerconnection.log
npm-debug.log
testem.log
/typings
/systemjs.config.js*

@ -0,0 +1,36 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: BaseApiController.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Microsoft.AspNetCore.Mvc;
namespace Ombi.Controllers
{
[Route("api/[controller]")]
public class BaseApiController : Controller
{
}
}

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace Ombi.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
return View();
}
public IActionResult Error()
{
return View();
}
}
}

@ -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);
}
}
}

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Ombi.Core;
using Ombi.Core.Models.Search;
namespace Ombi.Controllers
{
public class SearchController : BaseApiController
{
public SearchController(IMovieEngine movie)
{
MovieEngine = movie;
}
private IMovieEngine MovieEngine { get; }
[HttpGet("movie/{searchTerm}")]
public async Task<IEnumerable<SearchMovieViewModel>> SearchMovie(string searchTerm)
{
return await MovieEngine.ProcessMovieSearch(searchTerm);
}
[HttpGet("movie/popular")]
public async Task<IEnumerable<SearchMovieViewModel>> Popular()
{
return await MovieEngine.PopularMovies();
}
[HttpGet("movie/nowplaying")]
public async Task<IEnumerable<SearchMovieViewModel>> NowPlayingMovies()
{
return await MovieEngine.NowPlayingMovies();
}
[HttpGet("movie/toprated")]
public async Task<IEnumerable<SearchMovieViewModel>> TopRatedMovies()
{
return await MovieEngine.TopRatedMovies();
}
[HttpGet("movie/upcoming")]
public async Task<IEnumerable<SearchMovieViewModel>> UpcomingMovies()
{
return await MovieEngine.UpcomingMovies();
}
}
}

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<RuntimeIdentifiers>win10-x64;osx.10.12-x64;ubuntu.16.10-x64;debian.8-x64;</RuntimeIdentifiers>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="1.1.0" />
<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.VisualStudio.Web.BrowserLink" Version="1.1.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="ViewModels\**" />
<Content Remove="ViewModels\**" />
<EmbeddedResource Remove="ViewModels\**" />
<None Remove="ViewModels\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Core\Ombi.Core.csproj" />
<ProjectReference Include="..\Ombi.DependencyInjection\Ombi.DependencyInjection.csproj" />
<ProjectReference Include="..\Ombi.TheMovieDbApi\Ombi.TheMovieDbApi.csproj" />
</ItemGroup>
</Project>

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
namespace Ombi
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}

@ -0,0 +1,22 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:52038/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Ombi": {
"commandName": "Project"
}
}
}

@ -0,0 +1,81 @@
using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Ombi.DependencyInjection;
using Ombi.Store.Context;
namespace Ombi
{
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
services.RegisterDependencies(); // Ioc and EF
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".map"] = "application/octet-stream";
app.UseStaticFiles(new StaticFileOptions()
{
ContentTypeProvider = provider
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(Directory.GetCurrentDirectory(), @"app")),
RequestPath = new PathString("/app"),
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new { controller = "Home", action = "Index" });
});
}
}
}

@ -0,0 +1,242 @@
$primary-colour: #df691a;
$primary-colour-outline: #ff761b;
$bg-colour: #333333;
$bg-colour-disabled: #252424;
$i:
!important
;
.form-control-custom {
background-color: $bg-colour $i;
}
.form-control-custom-disabled {
background-color: $bg-colour-disabled $i;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: $primary-colour;
}
.scroll-top-wrapper {
background-color: $bg-colour;
}
.scroll-top-wrapper:hover {
background-color: $primary-colour;
}
body {
font-family: Open Sans Regular,Helvetica Neue,Helvetica,Arial,sans-serif;
color: #eee;
background-color: #1f1f1f;
}
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: #333;
}
.table-hover > tbody > tr:hover {
background-color: #282828;
}
fieldset {
padding: 15px;
}
legend {
border-bottom: 1px solid #333333;
}
.form-control {
color: #fefefe;
background-color: #333;
}
.radio input[type="radio"],
.radio-inline input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
margin-left: -0px;
}
.form-horizontal .radio,
.form-horizontal .checkbox,
.form-horizontal .radio-inline,
.form-horizontal .checkbox-inline {
margin-top: -15px;
}
.dropdown-menu {
background-color: #282828;
}
.dropdown-menu .divider {
background-color: #333333;
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #333;
}
.input-group-addon {
background-color: #333333;
}
.nav > li > a:hover,
.nav > li > a:focus {
background-color: #df691a;
}
.nav-tabs > li > a:hover {
border-color: #df691a #df691a transparent;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background-color: #df691a;
border: 1px solid #df691a;
}
.nav-tabs.nav-justified > .active > a,
.nav-tabs.nav-justified > .active > a:hover,
.nav-tabs.nav-justified > .active > a:focus {
border: 1px solid #df691a;
}
/*.navbar {
position: relative;
min-height: 40px;
margin-bottom: 21px;
z-index: 1000;
padding: 0px 3px;
font-size: 24px;
background-color: #000;
box-shadow: 0px 0px 0px 3px rgba(0, 0, 0, 0.2);
}*/
.navbar-default {
background-color: #0a0a0a;
border-color: #0a0a0a;
}
.navbar-default .navbar-brand {
color: #DF691A;
}
.nav-tabs {
border-bottom: 1px solid $bg-colour-disabled;
}
.navbar-default .navbar-nav > li > a:hover,
.navbar-default .navbar-nav > li > a:focus {
color: #F0ad4e;
background-color: #282828;
}
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
background-color: #282828;
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .open > a:hover,
.navbar-default .navbar-nav > .open > a:focus {
background-color: #df691a;
color: #fff;
}
.pagination > li > a,
.pagination > li > span {
background-color: #282828;
}
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
background-color: #333;
}
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #fefefe;
background-color: #333333;
}
.list-group-item {
background-color: #282828;
}
a.list-group-item:hover,
button.list-group-item:hover,
a.list-group-item:focus,
button.list-group-item:focus {
background-color: #333333;
}
.input-addon,
.input-group-addon {
color: #df691a;
}
.modal-header,
.modal-footer {
background-color: #282828;
}
.modal-content {
position: relative;
background-color: #282828;
border: 1px solid transparent;
border-radius: 0;
-webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
-webkit-background-clip: padding-box;
-moz-background-clip: padding-box;
background-clip: padding-box;
outline: 0;
}
.badge {
display: inline-block;
min-width: 10px;
padding: 3px 7px;
font-size: 12px;
font-weight: 300;
color: #ebebeb;
line-height: 1;
vertical-align: middle;
white-space: nowrap;
text-align: center;
background-color: $bg-colour;
border-radius: 10px;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
background-color: $bg-colour;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
border-bottom: 6px solid $bg-colour $i;
}
#sidebar-wrapper {
background: $bg-colour-disabled;
}
#cacherRunning {
background-color: $bg-colour;
text-align: center;
font-size: 15px;
padding: 3px 0;
}

@ -0,0 +1,7 @@
@import "./bootstrap.css";
//@import "./lib/tether.css";
@import "../node_modules/primeng/resources/themes/omega/theme.scss";
@import "./lib/primeng.css";
$fa-font-path: "../fonts/lib";
@import "../bower_components/font-awesome/scss/font-awesome.scss";

@ -0,0 +1,704 @@
@import './_imports.scss';
$form-color: #4e5d6c;
$form-color-lighter: #637689;
$primary-colour: #df691a;
$primary-colour-outline: #ff761b;
$info-colour: #5bc0de;
$warning-colour: #f0ad4e;
$danger-colour: #d9534f;
$success-colour: #5cb85c;
$i:!important;
@media (min-width: 768px ) {
.row {
position: relative;
}
.bottom-align-text {
position: absolute;
bottom: 0;
right: 0;
}
.landing-block .media {
max-width: 450px;
}
}
@media (max-width: 48em) {
.home {
padding-top: 1rem;
}
}
@media (min-width: 48em) {
.home {
padding-top: 4rem;
}
}
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
color: #fff;
}
hr {
border: 1px dashed #777;
}
.btn {
border-radius: .25rem $i;
}
.btn-group-separated .btn,
.btn-group-separated .btn + .btn {
margin-left: 3px;
}
.multiSelect {
background-color: $form-color;
}
.form-control-custom {
background-color: $form-color $i;
color: white $i;
border-radius: 0;
box-shadow: 0 0 0 !important;
}
h1 {
font-size: 3.5rem $i;
font-weight: 600 $i;
}
.request-title {
margin-top: 0 $i;
font-size: 1.9rem $i;
}
p {
font-size: 1.1rem $i;
}
label {
display: inline-block $i;
margin-bottom: .5rem $i;
font-size: 16px $i;
}
.small-label {
display: inline-block $i;
margin-bottom: .5rem $i;
font-size: 11px $i;
}
.small-checkbox{
min-height:0 $i;
}
.round-checkbox {
border-radius:8px;
}
.nav-tabs > li {
font-size: 13px;
line-height: 21px;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
background: #4e5d6c;
}
.nav-tabs > li > a > .fa {
padding: 3px 5px 3px 3px;
}
.nav-tabs > li.nav-tab-right {
float: right;
}
.nav-tabs > li.nav-tab-right a {
margin-right: 0;
margin-left: 2px;
}
.nav-tabs > li.nav-tab-icononly .fa {
padding: 3px;
}
.navbar .nav a .fa,
.dropdown-menu a .fa {
font-size: 130%;
top: 1px;
position: relative;
display: inline-block;
margin-right: 5px;
}
.dropdown-menu a .fa {
top: 2px;
}
.btn-danger-outline {
color: $danger-colour $i;
background-color: transparent;
background-image: none;
border-color: $danger-colour $i;
}
.btn-danger-outline:focus,
.btn-danger-outline.focus,
.btn-danger-outline:active,
.btn-danger-outline.active,
.btn-danger-outline:hover,
.open > .btn-danger-outline.dropdown-toggle {
color: #fff $i;
background-color: $danger-colour $i;
border-color: $danger-colour $i;
}
.btn-primary-outline {
color: $primary-colour-outline $i;
background-color: transparent;
background-image: none;
border-color: $primary-colour-outline $i;
}
.btn-primary-outline:focus,
.btn-primary-outline.focus,
.btn-primary-outline:active,
.btn-primary-outline.active,
.btn-primary-outline:hover,
.open > .btn-primary-outline.dropdown-toggle {
color: #fff $i;
background-color: $primary-colour $i;
border-color: $primary-colour $i;
}
.btn-info-outline {
color: $info-colour $i;
background-color: transparent;
background-image: none;
border-color: $info-colour $i;
}
.btn-info-outline:focus,
.btn-info-outline.focus,
.btn-info-outline:active,
.btn-info-outline.active,
.btn-info-outline:hover,
.open > .btn-info-outline.dropdown-toggle {
color: #fff $i;
background-color: $info-colour $i;
border-color: $info-colour $i;
}
.btn-warning-outline {
color: $warning-colour $i;
background-color: transparent;
background-image: none;
border-color: $warning-colour $i;
}
.btn-warning-outline:focus,
.btn-warning-outline.focus,
.btn-warning-outline:active,
.btn-warning-outline.active,
.btn-warning-outline:hover,
.open > .btn-warning-outline.dropdown-toggle {
color: #fff $i;
background-color: $warning-colour $i;
border-color: $warning-colour $i;
}
.btn-success-outline {
color: $success-colour $i;
background-color: transparent;
background-image: none;
border-color: $success-colour $i;
}
.btn-success-outline:focus,
.btn-success-outline.focus,
.btn-success-outline:active,
.btn-success-outline.active,
.btn-success-outline:hover,
.open > .btn-success-outline.dropdown-toggle {
color: #fff $i;
background-color: $success-colour $i;
border-color: $success-colour $i;
}
#movieList .mix {
display: none;
}
#tvList .mix {
display: none;
}
$border-radius: 10px;
.scroll-top-wrapper {
position: fixed;
opacity: 0;
visibility: hidden;
overflow: hidden;
text-align: center;
z-index: 99999999;
background-color: $form-color;
color: #eeeeee;
width: 50px;
height: 48px;
line-height: 48px;
right: 30px;
bottom: 30px;
padding-top: 2px;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
border-bottom-right-radius: $border-radius;
border-bottom-left-radius: $border-radius;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
}
.scroll-top-wrapper:hover {
background-color: $form-color-lighter;
}
.scroll-top-wrapper.show {
visibility: visible;
cursor: pointer;
opacity: 1.0;
}
.scroll-top-wrapper i.fa {
line-height: inherit;
}
.no-search-results {
text-align: center;
}
.no-search-results .no-search-results-icon {
font-size: 10em;
color: $form-color;
}
.no-search-results .no-search-results-text {
margin: 20px 0;
color: #ccc;
}
.form-control-search {
padding: 13px 105px 13px 16px;
height: 100%;
}
.form-control-withbuttons {
padding-right: 105px;
}
.input-group-addon .btn-group {
position: absolute;
right: 45px;
z-index: 3;
top: 10px;
box-shadow: 0 0 0;
}
.input-group-addon .btn-group .btn {
border: 1px solid rgba(255,255,255,.7) !important;
padding: 3px 12px;
color: rgba(255,255,255,.7) !important;
}
.btn-split .btn {
border-radius: 0 !important;
}
.btn-split .btn:not(.dropdown-toggle) {
border-radius: .25rem 0 0 .25rem $i;
}
.btn-split .btn.dropdown-toggle {
border-radius: 0 .25rem .25rem 0 $i;
padding: 12px 8px;
}
#updateAvailable {
background-color: #df691a;
text-align: center;
font-size: 15px;
padding: 3px 0;
}
#cacherRunning {
background-color: $form-color;
text-align: center;
font-size: 15px;
padding: 3px 0;
}
.checkbox label {
display: inline-block;
cursor: pointer;
position: relative;
padding-left: 25px;
margin-right: 15px;
font-size: 13px;
margin-bottom: 10px;
}
.checkbox label:before {
content: "";
display: inline-block;
width: 18px;
height: 18px;
margin-right: 10px;
position: absolute;
left: 0;
bottom: 1px;
border: 2px solid #eee;
border-radius: 3px;
}
.checkbox input[type=checkbox] {
display: none;
}
.checkbox input[type=checkbox]:checked + label:before {
content: "\2713";
font-size: 13px;
color: #fafafa;
text-align: center;
line-height: 13px;
}
.small-checkbox label {
display: inline-block;
cursor: pointer;
position: relative;
padding-left: 25px;
margin-right: 15px;
font-size: 13px;
margin-bottom: 10px;
}
.small-checkbox label:before {
content: "";
display: inline-block;
width: 18px;
height: 18px;
margin-right: 10px;
position: absolute;
left: 0;
bottom: 1px;
border: 2px solid #eee;
border-radius: 8px;
min-height:0px $i;
}
.small-checkbox input[type=checkbox] {
display: none;
}
.small-checkbox input[type=checkbox]:checked + label:before {
content: "\2713";
font-size: 13px;
color: #fafafa;
text-align: center;
line-height: 13px;
}
.small-checkbox label {
min-height: 0 $i;
padding-left: 20px;
margin-bottom: 0;
font-weight: normal;
cursor: pointer;
}
.input-group-sm {
padding-top: 2px;
padding-bottom: 2px;
}
.tab-pane .form-horizontal .form-group {
margin-right: 15px;
margin-left: 15px;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
background-color: $form-color;
}
.bootstrap-datetimepicker-widget.dropdown-menu.bottom:after {
border-bottom: 6px solid $form-color $i;
}
.bootstrap-datetimepicker-widget table td.active,
.bootstrap-datetimepicker-widget table td.active:hover {
color: #fff $i;
}
.landing-header {
display: block;
margin: 60px auto;
}
.landing-block {
background: #2f2f2f $i;
padding: 5px;
}
.landing-block .media {
margin: 30px auto;
max-width: 450px;
}
.landing-block .media .media-left {
display: inline-block;
float: left;
width: 70px;
}
.landing-block .media .media-left i.fa {
font-size: 3em;
}
.landing-title {
font-weight: bold;
}
.checkbox-custom {
margin-top: 0 $i;
margin-bottom: 0 $i;
}
.tooltip_templates {
display: none;
}
.shadow {
-moz-box-shadow: 3px 3px 5px 6px #191919;
-webkit-box-shadow: 3px 3px 5px 6px #191919;
box-shadow: 3px 3px 5px 6px #191919;
}
.img-circle {
border-radius: 50%;
}
#wrapper {
padding-left: 0;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
#wrapper.toggled {
padding-right: 250px;
}
#sidebar-wrapper {
z-index: 1000;
position: fixed;
right: 250px;
width: 0;
height: 100%;
margin-right: -250px;
overflow-y: auto;
background: #4e5d6c;
padding-left:0;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
#wrapper.toggled #sidebar-wrapper {
width: 500px;
}
#page-content-wrapper {
width: 100%;
position: absolute;
padding: 15px;
}
#wrapper.toggled #page-content-wrapper {
position: absolute;
margin-left: -250px;
}
/* Sidebar Styles */
.sidebar-nav {
position: absolute;
top: 0;
width: 500px;
margin: 0;
padding-left: 0;
list-style: none;
}
.sidebar-nav li {
text-indent: 20px;
line-height: 40px;
}
.sidebar-nav li a {
display: block;
text-decoration: none;
color: #999999;
}
.sidebar-nav li a:hover {
text-decoration: none;
color: #fff;
background: rgba(255,255,255,0.2);
}
.sidebar-nav li a:active,
.sidebar-nav li a:focus {
text-decoration: none;
}
.sidebar-nav > .sidebar-brand {
height: 65px;
font-size: 18px;
line-height: 60px;
}
.sidebar-nav > .sidebar-brand a {
color: #999999;
}
.sidebar-nav > .sidebar-brand a:hover {
color: #fff;
background: none;
}
@media(min-width:768px) {
#wrapper {
padding-right: 250px;
}
#wrapper.toggled {
padding-right: 0;
}
#sidebar-wrapper {
width: 500px;
}
#wrapper.toggled #sidebar-wrapper {
width: 0;
}
#page-content-wrapper {
padding: 20px;
position: relative;
}
#wrapper.toggled #page-content-wrapper {
position: relative;
margin-right: 0;
}
}
#lightbox {
background-color: grey;
filter:alpha(opacity=50); /* IE */
opacity: 0.5; /* Safari, Opera */
-moz-opacity:0.50; /* FireFox */
top: 0px;
left: 0px;
z-index: 20;
height: 100%;
width: 100%;
background-repeat:no-repeat;
background-position:center;
position:absolute;
}
.list-group-item-dropdown {
position: relative;
display: block;
padding: 10px 15px;
margin-bottom: -1px;
background-color: #3e3e3e;
border: 1px solid transparent;
}
.wizard-heading{
text-align: center;
}
.wizard-img{
width: 300px;
display: block $i;
margin: 0 auto $i;
}
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.pace-inactive {
display: none;
}
.pace .pace-progress {
background: $primary-colour;
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 5px;
}
.navbar-brand {
float: left;
font-size: 19px;
line-height: 21px;
height: 40px;
}
.gravatar{
border-radius:1em;
}
// Bootstrap overrides
html {
font-size: 10px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333;
background-color: #fff;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,26 @@
@*<!DOCTYPE html>*@
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Ombi</title>
<base href="@Url.Content("~/")" />
<script src="@Url.Content("~/lib/pace.js")?v=@ViewBag.AssemblyVersion"></script>
<link href="@Url.Content("~/css/lib/pace-theme-minimal.css")" rel="stylesheet" />
<link href="@Url.Content("~/css/base.css")" rel="stylesheet" type="text/css"/>
<link href="@Url.Content("~/css/Themes/plex.css")" rel="stylesheet" type="text/css" />
@*<link href="@Url.Content("~/css/lib/primeng.css")" rel="stylesheet" />*@
<script src="@Url.Content("~/lib/system.js")?v="></script>
<script src="@Url.Content("~/lib/jquery.js")?v="></script>
<script src="@Url.Content("~/lib/tether.js")?v="></script>
<script src="@Url.Content("~/lib/bootstrap.js")?v="></script>
<script src="@Url.Content("~/lib/systemjs.config.js")?v="></script>
</head>
<body>
@RenderBody()
<ombi></ombi>
</body>
</html>

@ -0,0 +1,2 @@
@using Ombi
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

@ -0,0 +1,31 @@

<nav class="navbar navbar-default navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" [routerLink]="['/']">Ombi</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li ><a [routerLink]="['/search']"><i class="fa fa-search"></i> Search</a></li>
</ul>
<ul class="nav navbar-nav">
<li ><a [routerLink]="['/settings/ombi']"><i class="fa fa-search"></i> Settings</a></li>
</ul>
</div>
</div>
</nav>
<div class="container" style="padding-top: 5%">
<router-outlet></router-outlet>
</div>

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './app.component.html'
})
export class AppComponent {
}

@ -0,0 +1,51 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { RouterModule, Routes } from '@angular/router';
import { HttpModule } from '@angular/http';
import { SearchComponent } from './search/search.component';
import { PageNotFoundComponent } from './errors/not-found.component';
// Services
import { SearchService } from './services/search.service';
import { RequestService } from './services/request.service';
// Modules
import { SettingsModule } from './settings/settings.module';
import { ButtonModule } from 'primeng/primeng';
import { GrowlModule } from 'primeng/components/growl/growl';
const routes: Routes = [
{ path: '*', component: PageNotFoundComponent },
{ path: 'search', component: SearchComponent }
];
@NgModule({
imports: [
RouterModule.forRoot(routes),
BrowserModule,
BrowserAnimationsModule,
HttpModule,
GrowlModule,
ButtonModule,
FormsModule,
SettingsModule
],
declarations: [
AppComponent,
PageNotFoundComponent,
SearchComponent
],
providers: [
SearchService,
RequestService
],
bootstrap: [AppComponent]
})
export class AppModule { }

@ -0,0 +1,27 @@
// Config
enum envs {
local = 0,
next = 1,
live = 2
}
var envVar = "#{Environment}";
var env = envs.local;
if (envs[envVar]) {
env = envs[envVar];
}
export var config = {
envs: envs,
env: env,
systemJS: {
bundle: <boolean>{
[envs.local]: false,
[envs.next]: true,
[envs.live]: true
}[env]
}
}
export default config;

@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
moduleId: module.id,
template: '<h2>Page not found</h2>'
})
export class PageNotFoundComponent { }

@ -0,0 +1,4 @@
export interface IRequestEngineResult {
requestAdded: boolean,
message: string
}

@ -0,0 +1,20 @@
export interface ISearchMovieResult {
backdropPath: string,
adult: boolean,
overview: string,
releaseDate: Date,
genreIds: number[],
id: number,
originalTitle: string,
originalLanguage: string,
title: string,
posterPath: string,
popularity: number,
voteCount: number,
video: boolean,
voteAverage: number,
alreadyInCp: boolean,
trailer: string,
homepage: string,
imdbId:string
}

@ -0,0 +1,13 @@
import './polyfills';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
import { config } from './config';
if (config.env !== config.envs.local) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);

@ -0,0 +1,14 @@
// TypeScript transpiles our app to ES5 but some dependencies are written in ES6 so must polyfill
import 'core-js/es6/string';
import 'core-js/es6/array';
import 'core-js/es6/object';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import { config } from './config';
if (config.env === config.envs.local) {
Error['stackTraceLimit'] = Infinity;
require('zone.js/dist/long-stack-trace-zone');
}

@ -0,0 +1,248 @@
<h1 id="searchTitle">Search</h1>
<h4>Search Paragraph</h4>
<br />
<!-- Nav tabs -->
<ul id="nav-tabs" class="nav nav-tabs" role="tablist">
<li role="presentation" class="active">
<a id="movieTabButton" href="#MoviesTab" aria-controls="home" role="tab" data-toggle="tab"><i class="fa fa-film"></i> Movies</a>
</li>
<li role="presentation">
<a id="actorTabButton" href="#ActorsTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-users"></i> Actors</a>
</li>
<li role="presentation">
<a id="tvTabButton" href="#TvShowTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-television"></i> TV Shows</a>
</li>
<!--
<li role="presentation">
<a href="#MusicTab" aria-controls="profile" role="tab" data-toggle="tab"><i class="fa fa-music"></i> Albums</a>
</li>-->
</ul>
<!-- Tab panes -->
<div class="tab-content">
<!-- Movie tab -->
<div role="tabpanel" class="tab-pane active" id="MoviesTab">
<div class="input-group">
<input id="movieSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a (click)="popularMovies()">Popular Movies</a></li>
<li><a (click)="upcomingMovies()">Upcoming Movies</a></li>
<li><a (click)="topRatedMovies()">Top Rated Movies</a></li>
<li><a (click)="nowPlayingMovies()">Now Playing Movies</a></li>
</ul>
</div>
<i id="movieSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- Movie content -->
<div id="movieList">
</div>
</div>
<!-- Actors tab -->
<div role="tabpanel" class="tab-pane" id="ActorsTab">
<div class="input-group">
<input id="actorSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons">
<div class="input-group-addon">
<i id="actorSearchButton" class="fa fa-search"></i>
</div>
</div>
<div class="checkbox">
<input type="checkbox" id="actorsSearchNew" name="actorsSearchNew"><label for="actorsSearchNew">@UI.Search_NewOnly</label>
</div>
<br />
<br />
<!-- Movie content -->
<div id="actorMovieList">
</div>
</div>
<!-- TV tab -->
<div role="tabpanel" class="tab-pane" id="TvShowTab">
<div class="input-group">
<input id="tvSearchContent" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons">
<div class="input-group-addon">
<div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
@UI.Search_Suggestions
<i class="fa fa-chevron-down"></i>
</a>
<ul class="dropdown-menu">
<li><a id="popularShows" >Popular Shows</a></li>
<li><a id="trendingShows" href="#">Trending Shows</a></li>
<li><a id="mostWatchedShows" href="#">Most Watched Shows</a></li>
<li><a id="anticipatedShows" href="#">Most Anticipated Shows</a></li>
</ul>
</div><i id="tvSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<!-- TV content -->
<div id="tvList">
</div>
</div>
<!-- Music tab -->
<!-- <div role="tabpanel" class="tab-pane" id="MusicTab">
<div class="input-group">
<input id="musicSearchContent" type="text" class="form-control form-control-custom form-control-search">
<div class="input-group-addon">
<i id="musicSearchButton" class="fa fa-search"></i>
</div>
</div>
<br />
<br />
<div id="musicList">
</div>
</div>
-->
<div *ngFor="let result of movieResults">
<div class="row">
<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">
</div>
<div class="col-sm-8">
<div>
<a href="https://www.themoviedb.org/movie/{{result.id}}/" target="_blank">
<h4>{{result.title}} ({{result.releaseDate}})</h4>
</a>
<span *ngIf="result.firstAired" class="label label-info" target="_blank">Air Date: {{result.firstAired}}</span>
<span *ngIf="result.releaseDate" class="label label-info" target="_blank">Release Date: {{result.releaseDate}}</span>
<span *ngIf="result.available" class="label label-success">@UI.Search_Available</span>
<span *ngIf="result.approved" class="label label-info">@UI.Search_Processing_Request</span>
<div *ngIf="result.requested; then requested else notRequested"></div>
<template #requested>
<span class="label label-warning">Pending Approval</span>
</template>
<template #notRequested>
<span class="label label-danger">Not Yet Requested</span>
</template>
<span id="{{id}}netflixTab"></span>
<a *ngIf="result.homepage" href="{{result.homepage}}" target="_blank"><span class="label label-info">HomePage</span></a>
<a *ngIf="result.trailer" href="{{result.trailer}}" target="_blank"><span class="label label-info">Trailer</span></a>
<br />
<br />
</div>
<p style="font-size:0.9rem !important">{{result.overview}}</p>
</div>
<div class="col-sm-2">
<input name="{{type}}Id" type="text" value="{{result.id}}" hidden="hidden" />
<div *ngIf="result.available">
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> Available</button>
<div *ngIf="result.url">
<br />
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{result.url}}" target="_blank"><i class="fa fa-eye"></i> View In Plex</a>
</div>
</div>
<div *ngIf="result.requested; then requestedBtn else notRequestedBtn"></div>
<template #requestedBtn>
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i class="fa fa-check"></i> Requested</button>
</template>
<template #notRequestedBtn>
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline" (click)="request(result)"><i class="fa fa-plus"></i> Request</button>
</template>
<!--{{#if_eq type "tv"}}
{{#if_eq tvFullyAvailable true}}
@*//TODO Not used yet*@
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> @UI.Search_Available</button><br />
{{else}}
{{#if_eq enableTvRequestsForOnlySeries true}}
<button id="{{id}}" style="text-align: right" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline dropdownTv{{/if}} btn-primary-outline" season-select="0" type="button" {{#if available}} disabled{{/if}}><i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request{{/if}}</button>
{{else}}
<div class="dropdown">
<button id="{{id}}" class="btn {{#if available}}btn-success-outline{{else}}btn-primary-outline{{/if}} dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> {{#if available}}@UI.Search_Available{{else}}@UI.Search_Request {{/if}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a id="{{id}}" season-select="0" class="dropdownTv " href="#">@UI.Search_AllSeasons</a></li>
{{#if_eq disableTvRequestsBySeason false}}
<li><a id="{{id}}" season-select="1" class="dropdownTv" href="#">@UI.Search_FirstSeason</a></li>
<li><a id="{{id}}" season-select="2" class="dropdownTv" href="#">@UI.Search_LatestSeason</a></li>
<li><a id="SeasonSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#seasonsModal" href="#">@UI.Search_SelectSeason...</a></li>
{{/if_eq}}
{{#if_eq disableTvRequestsByEpisode false}}
<li><a id="EpisodeSelect" data-identifier="{{id}}" data-toggle="modal" data-target="#episodesModal" href="#">@UI.Search_SelectEpisode...</a></li>
{{/if_eq}}
</ul>
</div>
{{/if_eq}}
{{#if available}}
{{#if url}}
<br />
<a style="text-align: right" class="btn btn-sm btn-primary-outline" href="{{url}}" target="_blank"><i class="fa fa-eye"></i> @UI.Search_ViewInPlex</a>
{{/if}}
{{/if}}
{{/if_eq}}
{{/if_eq}}-->
<br />
<div *ngIf="result.available">
<input name="providerId" type="text" value="{{id}}" hidden="hidden" />
<input name="type" type="text" value="{{type}}" hidden="hidden" />
<div class="dropdown">
<button class="btn btn-sm btn-danger-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-exclamation"></i> @UI.Search_ReportIssue
<span class="caret"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
<li><a issue-select="0" class="dropdownIssue" href="#">@UI.Issues_WrongAudio</a></li>
<li><a issue-select="1" class="dropdownIssue" href="#">@UI.Issues_NoSubs</a></li>
<li><a issue-select="2" class="dropdownIssue" href="#">@UI.Issues_WrongContent</a></li>
<li><a issue-select="3" class="dropdownIssue" href="#">@UI.Issues_Playback</a></li>
<li><a issue-select="4" class="dropdownIssue" href="#" data-toggle="modal" data-target="#issuesModal">@UI.Issues_Other</a></li>
</ul>
</div>
</div>
</div>
</div>
<hr />
</div>

@ -0,0 +1,78 @@
import { Component, OnInit } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import 'rxjs/add/operator/map';
import { SearchService } from '../services/search.service';
import { RequestService } from '../services/request.service';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
import { IRequestEngineResult } from '../interfaces/IRequestEngineResult';
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './search.component.html',
providers: [SearchService, RequestService]
})
export class SearchComponent implements OnInit {
searchText: string;
searchChanged: Subject<string> = new Subject<string>();
movieResults: ISearchMovieResult[];
result: IRequestEngineResult;
constructor(private searchService: SearchService, private requestService: RequestService) {
this.searchChanged
.debounceTime(600) // Wait Xms afterthe last event before emitting last event
.distinctUntilChanged() // only emit if value is different from previous value
.subscribe(x => {
this.searchText = x as string;
if (this.searchText === "") {
this.clearResults();
return;
}
this.searchService.searchMovie(this.searchText).subscribe(x => this.movieResults = x);
});
}
ngOnInit(): void {
this.searchText = "";
this.movieResults = [];
this.result = {
message: "",
requestAdded:false
}
}
search(text: any) {
this.searchChanged.next(text.target.value);
}
request(searchResult: ISearchMovieResult) {
this.requestService.requestMovie(searchResult).subscribe(x => this.result = x);
}
popularMovies() {
this.clearResults();
this.searchService.popularMovies().subscribe(x => this.movieResults = x);
}
nowPlayingMovies() {
this.clearResults();
this.searchService.nowPlayingMovies().subscribe(x => this.movieResults = x);
}
topRatedMovies() {
this.clearResults();
this.searchService.topRatedMovies().subscribe(x => this.movieResults = x);
}
upcomingMovies() {
this.clearResults();
this.searchService.upcomignMovies().subscribe(x => this.movieResults = x);
}
private clearResults() {
this.movieResults = [];
}
}

@ -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);
}
}

@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { ServiceHelpers } from './service.helpers';
import { ISearchMovieResult } from '../interfaces/ISearchMovieResult';
@Injectable()
export class SearchService {
constructor(private http: Http) {
}
searchMovie(searchTerm: string): Observable<ISearchMovieResult[]> {
return this.http.get('/api/Search/Movie/' + searchTerm).map(ServiceHelpers.extractData);
}
popularMovies(): Observable<ISearchMovieResult[]> {
return this.http.get('/api/Search/Movie/Popular').map(ServiceHelpers.extractData);
}
upcomignMovies(): Observable<ISearchMovieResult[]> {
return this.http.get('/api/Search/Movie/upcoming').map(ServiceHelpers.extractData);
}
nowPlayingMovies(): Observable<ISearchMovieResult[]> {
return this.http.get('/api/Search/Movie/nowplaying').map(ServiceHelpers.extractData);
}
topRatedMovies(): Observable<ISearchMovieResult[]> {
return this.http.get('/api/Search/Movie/toprated').map(ServiceHelpers.extractData);
}
}

@ -0,0 +1,15 @@
import { Headers, RequestOptions, Response } from '@angular/http';
export class ServiceHelpers {
public static Headers = new Headers({ 'Content-Type': 'application/json' });
public static RequestOptions = new RequestOptions({
headers: ServiceHelpers.Headers
});
public static extractData(res: Response) {
console.log(res);
return res.json();
}
}

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'ombi',
moduleId: module.id,
templateUrl: './ombi.component.html',
})
export class OmbiComponent {
}

@ -0,0 +1,29 @@
import { NgModule, } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule, Routes } from '@angular/router';
import { OmbiComponent } from './ombi/ombi.component'
const routes: Routes = [
{ path: 'Settings/Ombi', component: OmbiComponent }
];
@NgModule({
imports: [
CommonModule,
FormsModule,
RouterModule.forChild(routes),
],
declarations: [
OmbiComponent
],
exports: [
RouterModule
],
providers: [
]
})
export class SettingsModule { }

@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

@ -0,0 +1,8 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

@ -0,0 +1,9 @@
{
"name": "ombi",
"private": true,
"dependencies": {
"PACE": "pace#^1.0.2",
"font-awesome": "^4.7.0"
}
}

@ -0,0 +1,24 @@
// Configure bundling and minification for the project.
// More info at https://go.microsoft.com/fwlink/?LinkId=808241
[
{
"outputFileName": "wwwroot/css/site.min.css",
// An array of relative input file paths. Globbing patterns supported
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
// Optionally specify minification options
"minify": {
"enabled": true,
"renameLocals": true
},
// Optionally generate .map file
"sourceMap": false
}
]

@ -0,0 +1,270 @@
/// <binding BeforeBuild='build' ProjectOpened='watch' />
'use strict';
var gulp = require('gulp');
var sass = require('gulp-sass');
var changed = require('gulp-changed');
var rename = require('gulp-rename');
//var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var path = require('path');
var del = require('del');
var merge = require('merge-stream');
var gulpif = require('gulp-if');
var runSequence = require('run-sequence');
var cleancss = require('gulp-clean-css');
var filter = require('gulp-filter');
var systemJSBuilder = require('systemjs-builder');
var run = require('gulp-run');
var paths = {
wwwroot: './wwwroot/',
npm: { // These will be resolved automatically and copied to output directory as its name, only works for pre-bundled modules e.g. angular
src: [
'@angular/animations',
'@angular/animations/browser',
'@angular/core',
'@angular/common',
'@angular/compiler',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/http',
'@angular/router',
'@angular/forms'
],
dest: './lib'
},
lib: { // These are simple single-file dependencies with optional rename, for more files or folders use modules
src: [
{
file: './node_modules/@angular/platform-browser/bundles/platform-browser-animations.umd.js',
rename: '@angular/platform-browser/animations'
},
{
file: './node_modules/systemjs/dist/system.src.js',
rename: 'system'
},
{
file: './node_modules/systemjs/dist/system-polyfills.src.js',
rename: 'system-polyfills'
},
{
file: './node_modules/jquery/dist/jquery.min.js',
rename: 'jquery'
},
'./bower_components/PACE/pace.js',
'./node_modules/bootstrap/dist/js/bootstrap.js',
'./node_modules/tether/dist/js/tether.js',
'./systemjs.config.js'
],
dest: './lib/'
},
libcss: [ // Normal css files to be copied
{
src: [
'./bower_components/PACE/themes/purple/pace-theme-minimal.css',
'./bower_components/font-awesome/css/font-awesome.css',
'./node_modules/primeng/resources/primeng.css',
'./node_modules/tether/dist/css/tether.css'
],
dest: './css/lib/'
},
{
src: './Styles/**/*.css',
dest: './css',
filter: '**/*.css'
}
],
libfonts: [ // Library fonts
{
src: [
'./bower_components/font-awesome/fonts/*'
],
dest: './fonts/lib/'
},
{
src: [
'./node_modules/primeng/resources/themes/omega/fonts/*'
],
dest: './fonts/'
}
],
libimages: [ // Library images
{
src: [
'./node_modules/primeng/resources/themes/omega/images/*'
],
dest: './images/'
}
],
modules: [ // This is for modules with multiple files that require each other, used when npm can't be used
{
name: 'zone.js',
src: ['./node_modules/zone.js/**/*.js'],
dest: './lib/zone.js/'
},
{
name: 'rxjs',
src: ['./node_modules/rxjs/**/*.js', '!./node_modules/rxjs/src/**/*.js'],
dest: './lib/rxjs/'
},
{
name: 'core-js',
src: ['./node_modules/core-js/**/*.js'],
dest: './lib/core-js/'
},
{
name: 'primeng',
src: './node_modules/primeng/**/*.js',
dest: './lib/primeng/'
}
],
sass: { // Simple sass->css compilation
src: ['./Styles/**/*.scss', '!./Styles/primeng/**'],
dest: './css/',
filter: '**/*.css'
},
bundle: { // This is the config for the bundler, you shouldn't need to change this
root: './',
dest: './lib/bundle.js',
bundle: 'app/main.js',
}
}
gulp.task('npm', function () {
var streams = []
for (let module of paths.npm.src) {
let file = require.resolve(module);
streams.push(
gulp.src(file)
.pipe(gulpif(global.full, sourcemaps.init()))
//.pipe(gulpif(global.full, uglify({ source_map: true })))
.pipe(rename((path => { path.basename = module })))
.pipe(gulpif(global.full, sourcemaps.write('../maps')))
.pipe(gulp.dest(path.join(paths.wwwroot, paths.npm.dest)))
);
}
return merge(streams);
})
gulp.task('lib', function () {
var streams = []
for (let module of paths.lib.src) {
streams.push(
gulp.src(typeof module === "string" ? module : module.file)
.pipe(gulpif(global.full, sourcemaps.init()))
//.pipe(gulpif(global.full, uglify({ source_map: true })))
.pipe(rename(function (path) {
if (typeof module !== "string" && module.rename) {
path.basename = module.rename;
}
}))
.pipe(gulpif(global.full, sourcemaps.write('maps')))
.pipe(gulp.dest(path.join(paths.wwwroot, paths.lib.dest)))
);
}
return merge(streams);
})
gulp.task('libcss', function () {
var streams = []
for (let module of paths.libcss) {
var f = filter("**/*.css", { restore: true });
streams.push(
gulp.src(module.src)
.pipe(f)
.pipe(gulpif(global.full, sourcemaps.init()))
.pipe(gulpif(global.full, cleancss()))
.pipe(gulpif(global.full, sourcemaps.write(`${module.name ? '.' : ''}./maps/${module.name ? module.name : ''}`)))
.pipe(f.restore)
.pipe(gulp.dest(path.join(paths.wwwroot, module.dest)))
);
}
return merge(streams);
})
gulp.task('libfonts', function () {
var streams = []
for (let module of paths.libfonts) {
streams.push(
gulp.src(module.src)
.pipe(gulp.dest(path.join(paths.wwwroot, module.dest)))
);
}
return merge(streams);
})
gulp.task('libimages', function () {
var streams = []
for (let module of paths.libimages) {
streams.push(
gulp.src(module.src)
.pipe(gulp.dest(path.join(paths.wwwroot, module.dest)))
);
}
return merge(streams);
})
gulp.task('modules', function () {
var streams = []
for (let module of paths.modules) {
streams.push(
gulp.src(module.src)
.pipe(gulpif(global.full, sourcemaps.init()))
// .pipe(gulpif(global.full, uglify({ source_map: true })))
.pipe(gulpif(global.full, sourcemaps.write(`${module.name ? '.' : ''}./maps/${module.name ? module.name : ''}`)))
.pipe(gulp.dest(path.join(paths.wwwroot, module.dest)))
);
}
return merge(streams);
})
gulp.task('sass', function () {
return gulp.src(paths.sass.src)
.pipe(changed(paths.sass.dest))
.pipe(gulpif(global.full, sourcemaps.init()))
.pipe(sass({ outputStyle: global.full ? 'compressed' : 'nested' }).on('error', sass.logError))
.pipe(gulpif(global.full, sourcemaps.write('maps')))
.pipe(gulp.dest(path.join(paths.wwwroot, paths.sass.dest)))
});
gulp.task('bundle', function () {
var builder = new systemJSBuilder(paths.bundle.root);
builder.config({
baseURL: paths.wwwroot,
packages: {
'.': {
defaultExtension: 'js'
}
},
paths: {
'*': 'lib/*',
'app/*': 'app/*'
}
});
del.sync(path.join(paths.wwwroot, paths.bundle.dest), { force: true });
return builder.bundle(paths.bundle.bundle, path.join(paths.wwwroot, paths.bundle.dest), {
sourceMaps: true
})
})
gulp.task('typescript', function () {
return run('tsc').exec();
});
gulp.task('fullvar', () => { global.full = true });
gulp.task('libs')
gulp.task('copy', ['lib', 'libcss', 'libfonts', 'libimages', 'npm', 'modules']);
gulp.task('compile', ['sass']);
gulp.task('build', callback => runSequence('copy', 'compile', callback));
gulp.task('full', callback => runSequence('build', callback));
// Use this in a build server environment to compile and bundle everything
gulp.task('publish', callback => runSequence('fullvar', 'full', 'typescript', 'bundle', callback));
// Auto compiles sass files on change, note that this doesn't seem to pick up new files at the moment
gulp.task('watch', function () {
gulp.watch(paths.sass.src, ['sass']);
gulp.watch('./Styles/**/*.css', ['libcss']); // legacy css
});

@ -0,0 +1,45 @@
{
"name": "ombi",
"version": "1.0.0",
"private": true,
"dependencies": {
"@angular/animations": "^4.0.0",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/compiler-cli": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/platform-server": "^4.0.0",
"@angular/router": "^4.0.0",
"@types/jquery": "^2.0.33",
"@types/systemjs": "^0.20.2",
"core-js": "^2.4.1",
"del": "^2.2.2",
"gulp": "~3.9.1",
"gulp-changed": "^1.3.0",
"gulp-clean-css": "^3.0.4",
"gulp-filter": "^5.0.0",
"gulp-if": "^2.0.2",
"gulp-rename": "^1.2.2",
"gulp-run": "^1.7.1",
"gulp-sass": "^2.3.2",
"gulp-sourcemaps": "^1.9.0",
"gulp-systemjs-builder": "^0.15.0",
"gulp-uglify": "^2.1.2",
"jquery": "2.2.1",
"merge-stream": "^1.0.1",
"nanoscroller": "^0.8.7",
"primeng": "^2.0.5",
"run-sequence": "^1.2.2",
"rxjs": "^5.0.3",
"systemjs": "^0.19.41",
"systemjs-builder": "^0.15.34",
"tether": "^1.4.0",
"typescript": "^2.2.1",
"zone.js": "^0.8.5",
"bootstrap": "3.3.6"
}
}

@ -0,0 +1,20 @@
System.import('/app/config.js').then((module: any) => {
var config = module.config.systemJS;
System.config({
baseURL: '/lib',
packages: {
'.': {
defaultExtension: 'js'
}
},
map: { app: '../app' }
})
if (config.bundle) {
System.import('bundle').then(() => {
System.import('/app/main');
})
} else {
System.import('/app/main')
}
});

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": [ "es5", "es2015" ],
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"alwaysStrict": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"skipLibCheck": true
},
"compileOnSave": true
}

@ -1,21 +1,56 @@
version: 2.2.{build}
version: 3.0.{build}
configuration: Release
os: Visual Studio 2015 Preview
assembly_info:
patch: true
file: '**\AssemblyInfo.*'
assembly_version: '2.2.1'
assembly_version: '3.0.0'
assembly_file_version: '{version}'
assembly_informational_version: '2.2.1'
assembly_informational_version: '3.0.0'
before_build:
- cmd: appveyor-retry nuget restore
build:
verbosity: minimal
- cmd: cd ombi/ombi
- appveyor-retry dotnet restore
- appveyor-retry npm install bower -g
- appveyor-retry npm install -g gulp
- appveyor-retry npm install -g typescript
- appveyor-retry npm install
- appveyor-retry bower install
- gulp publish
build_script:
- dotnet build
after_build:
- dotnet publish -c Release /p:AppRuntimeIdentifier=win10-x64
- dotnet publish -c Release /p:AppRuntimeIdentifier=osx.10.12-x64
- dotnet publish -c Release /p:AppRuntimeIdentifier=ubuntu.16.10-x64
- dotnet publish -c Release /p:AppRuntimeIdentifier=debian.8-x64
- cmd: >-
7z a Ombi.zip %APPVEYOR_BUILD_FOLDER%\Ombi.UI\bin\Release\
7z a Ombi_windows.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\win10-x64\publish
7z a Ombi_osx.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\osx.10.12-x64\publish
7z a Ombi_ubuntu.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\ubuntu.16.10-x64\publish
7z a Ombi_debian.zip %APPVEYOR_BUILD_FOLDER%\Ombi\Ombi\bin\Release\netcoreapp1.1\debian.8-x64\publish
appveyor PushArtifact Ombi.zip
appveyor PushArtifact Ombi_windows.zip
appveyor PushArtifact Ombi_osx.zip
appveyor PushArtifact Ombi_ubuntu.zip
appveyor PushArtifact Ombi_debian.zip
cache:
- '%USERPROFILE%\.nuget\packages'
deploy:
- provider: GitHub
release: Ombi v$(appveyor_build_version)
@ -24,7 +59,3 @@ deploy:
draft: true
on:
branch: master
- provider: Environment
name: Microserver
on:
branch: dev

Loading…
Cancel
Save