mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-08-22 14:13:36 -07:00
Compare commits
26 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b72f47470c | ||
|
72d4115378 |
||
|
11fd7a5fc8 |
||
|
d2be48a921 | ||
|
a92c76021a | ||
|
97d5167db6 | ||
|
2519cca9f6 | ||
|
cfeee39978 | ||
|
cee40146ee | ||
|
1eff48e58e | ||
|
3b2a0d84be | ||
|
ed5bc3f873 | ||
|
067c029f42 | ||
|
cfe2b6ac0f | ||
|
c9ab4f4f9f | ||
|
acb679f99d | ||
|
f88c5ad818 | ||
|
b3e8ca6950 |
||
|
15a97794f6 | ||
|
ba6e708e18 | ||
|
08c9017a2c | ||
|
f8658fe6d5 | ||
|
7303e7da3b | ||
|
ffb495019f | ||
|
dbbfdd926f | ||
|
53a6a092b1 |
24 changed files with 574 additions and 178 deletions
164
CHANGELOG.md
164
CHANGELOG.md
|
@ -1,3 +1,81 @@
|
||||||
|
## [4.49.3](https://github.com/Ombi-app/Ombi/compare/v4.49.2...v4.49.3) (2025-08-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **plex-api:** update Plex Watchlist URL ([11fd7a5](https://github.com/Ombi-app/Ombi/commit/11fd7a5fc853da75974a16bf4fdecd72a836f54b))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.49.2](https://github.com/Ombi-app/Ombi/compare/v4.49.1...v4.49.2) (2025-07-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **discover:** :zap: Improve the loading performance on the discover page ([97d5167](https://github.com/Ombi-app/Ombi/commit/97d5167db6c9f915021f32b96b281d7db3741d7f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.49.1](https://github.com/Ombi-app/Ombi/compare/v4.49.0...v4.49.1) (2025-07-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **auth:** Fixed an issue where refreshing the page as a power user would stop the application from loading [#5242](https://github.com/Ombi-app/Ombi/issues/5242) ([cee4014](https://github.com/Ombi-app/Ombi/commit/cee40146ee02f7fb79e2019d6fe2f9d5c5dbdfc8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [4.49.0](https://github.com/Ombi-app/Ombi/compare/v4.48.5...v4.49.0) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Added the ability for the Watchlist to automatically refresh the users token. This will reduce the need for the user to log in ([067c029](https://github.com/Ombi-app/Ombi/commit/067c029f42e9fd853d060fdb2093013b15ac14c0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.48.5](https://github.com/Ombi-app/Ombi/compare/v4.48.4...v4.48.5) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* filter out excluded notification agents from user preferences ([c9ab4f4](https://github.com/Ombi-app/Ombi/commit/c9ab4f4f9faa66dbf263da693db1eefcf68beeec)), closes [#5196](https://github.com/Ombi-app/Ombi/issues/5196)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.48.4](https://github.com/Ombi-app/Ombi/compare/v4.48.3...v4.48.4) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **translations:** 🌐 New translations from Crowdin [skip ci] ([dbbfdd9](https://github.com/Ombi-app/Ombi/commit/dbbfdd926f0808f6d16f0b2cd8b5406e6b610c82))
|
||||||
|
* **translations:** 🌐 New translations from Crowdin [skip ci] ([53a6a09](https://github.com/Ombi-app/Ombi/commit/53a6a092b14b8b8bdbff95d066926d3dbe6951f4))
|
||||||
|
* **ui:** correct timezone handling in OmbiDatePipe ([f88c5ad](https://github.com/Ombi-app/Ombi/commit/f88c5ad818fadea7064e7dfbe46f07eae855109a)), closes [#5102](https://github.com/Ombi-app/Ombi/issues/5102)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.48.3](https://github.com/Ombi-app/Ombi/compare/v4.48.2...v4.48.3) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Correct 4K movie request existence check ([ba6e708](https://github.com/Ombi-app/Ombi/commit/ba6e708e189f52f2ff4ebc073fa38a4f53f1061c)), closes [#4798](https://github.com/Ombi-app/Ombi/issues/4798)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.48.2](https://github.com/Ombi-app/Ombi/compare/v4.48.1...v4.48.2) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **radarr:** ensure RequestedUser is loaded when creating tags ([f8658fe](https://github.com/Ombi-app/Ombi/commit/f8658fe6d56488aa5caa68093245cbf021a31810)), closes [#5045](https://github.com/Ombi-app/Ombi/issues/5045)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [4.48.1](https://github.com/Ombi-app/Ombi/compare/v4.48.0...v4.48.1) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [4.48.0](https://github.com/Ombi-app/Ombi/compare/v4.47.3...v4.48.0) (2025-05-14)
|
# [4.48.0](https://github.com/Ombi-app/Ombi/compare/v4.47.3...v4.48.0) (2025-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2133,89 +2211,3 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.43.5](https://github.com/Ombi-app/Ombi/compare/v4.43.4...v4.43.5) (2023-08-24)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.43.4](https://github.com/Ombi-app/Ombi/compare/v4.43.3...v4.43.4) (2023-07-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **user-importer:** Fixed not importing all correct users [#4989](https://github.com/Ombi-app/Ombi/issues/4989) ([34c32f8](https://github.com/Ombi-app/Ombi/commit/34c32f8338705ea3f790d95b91c9ada21a41b9f2))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.43.3](https://github.com/Ombi-app/Ombi/compare/v4.43.2...v4.43.3) (2023-07-28)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* switch back to the old plex friends API [#4989](https://github.com/Ombi-app/Ombi/issues/4989) ([c8ad12e](https://github.com/Ombi-app/Ombi/commit/c8ad12eb5f53889609d1793ae907afd33ba6ef38))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.43.2](https://github.com/Ombi-app/Ombi/compare/v4.43.1...v4.43.2) (2023-07-19)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **plex-api:** Switch over to the new API to avoid deprecation & save… ([#4986](https://github.com/Ombi-app/Ombi/issues/4986)) ([2f2d35e](https://github.com/Ombi-app/Ombi/commit/2f2d35ec867a8e5488e368db294bd37bcf92d843))
|
|
||||||
* Remove old trending source ([#4987](https://github.com/Ombi-app/Ombi/issues/4987)) ([aacaa3e](https://github.com/Ombi-app/Ombi/commit/aacaa3e140b43f5d196da612f785cc4451717752))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.43.1](https://github.com/Ombi-app/Ombi/compare/v4.43.0...v4.43.1) (2023-07-16)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **user-importer:** don't delete admins in the cleanup ([895b9bf](https://github.com/Ombi-app/Ombi/commit/895b9bf6a060a678d4b0cca8083aa96c38e47b95))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [4.43.0](https://github.com/Ombi-app/Ombi/compare/v4.42.3...v4.43.0) (2023-07-14)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Add Auto Approve 4K role ([#4982](https://github.com/Ombi-app/Ombi/issues/4982)) ([#4983](https://github.com/Ombi-app/Ombi/issues/4983)) ([ac05495](https://github.com/Ombi-app/Ombi/commit/ac054954254b9d77a42e057f1065570c7fdc1093)), closes [#4957](https://github.com/Ombi-app/Ombi/issues/4957)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.42.3](https://github.com/Ombi-app/Ombi/compare/v4.42.2...v4.42.3) (2023-07-13)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **user-importer:** Do not delete the Plex Admin as part of the user Importer cleanup [#4870](https://github.com/Ombi-app/Ombi/issues/4870) ([#4981](https://github.com/Ombi-app/Ombi/issues/4981)) ([4e80e7b](https://github.com/Ombi-app/Ombi/commit/4e80e7b7c3239a46a645ab6d1054993734ad4dd6))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.42.2](https://github.com/Ombi-app/Ombi/compare/v4.42.1...v4.42.2) (2023-07-03)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* Remove Angular TSLint ([#4973](https://github.com/Ombi-app/Ombi/issues/4973)) ([93969b5](https://github.com/Ombi-app/Ombi/commit/93969b5a2d82f442299bee418fae43cb590d7743))
|
|
||||||
* upgrade jquery from 3.6.1 to 3.7.0 ([#4974](https://github.com/Ombi-app/Ombi/issues/4974)) ([f2552ef](https://github.com/Ombi-app/Ombi/commit/f2552ef6ede011080a8d5499e11930c4d41d04c2))
|
|
||||||
* upgrade multiple dependencies with Snyk ([#4961](https://github.com/Ombi-app/Ombi/issues/4961)) ([3c3edf6](https://github.com/Ombi-app/Ombi/commit/3c3edf6273fa98c420989ebcebfee52b2545e402))
|
|
||||||
* upgrade zone.js from 0.11.8 to 0.13.0 ([#4975](https://github.com/Ombi-app/Ombi/issues/4975)) ([37f6564](https://github.com/Ombi-app/Ombi/commit/37f65648a2f8742020b0954acec4168aee048942))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [4.42.1](https://github.com/Ombi-app/Ombi/compare/v4.42.0...v4.42.1) (2023-06-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* More automation tests mainly around the Plex Settings page ([#4821](https://github.com/Ombi-app/Ombi/issues/4821)) ([21bfc5a](https://github.com/Ombi-app/Ombi/commit/21bfc5a45adf6da6a80854e19494a8ffdc9c0761))
|
|
||||||
* src/Ombi.Notifications/Ombi.Notifications.csproj to reduce vulnerabilities ([#4969](https://github.com/Ombi-app/Ombi/issues/4969)) [skip ci] ([8584ad4](https://github.com/Ombi-app/Ombi/commit/8584ad46053c51f5da40b24f3efd1b9e5a031ddd))
|
|
||||||
* upgrade @fortawesome/fontawesome-free from 6.1.2 to 6.4.0 ([#4965](https://github.com/Ombi-app/Ombi/issues/4965)) [skip ci] ([84454e5](https://github.com/Ombi-app/Ombi/commit/84454e53c00c808e8a393c7750bdc418a7593e91))
|
|
||||||
* upgrade @microsoft/signalr from 6.0.11 to 6.0.16 ([#4964](https://github.com/Ombi-app/Ombi/issues/4964)) [skip ci] ([a0201e3](https://github.com/Ombi-app/Ombi/commit/a0201e3f585dc52f717e33c46ede35a4eccac736))
|
|
||||||
* upgrade cypress-real-events from 1.7.4 to 1.8.1 ([#4968](https://github.com/Ombi-app/Ombi/issues/4968)) [skip ci] ([8a24b56](https://github.com/Ombi-app/Ombi/commit/8a24b56299c3bc98bf0d719ba448972aaa7f7461))
|
|
||||||
* upgrade multiple dependencies with Snyk ([#4963](https://github.com/Ombi-app/Ombi/issues/4963)) [skip ci] ([6025c5e](https://github.com/Ombi-app/Ombi/commit/6025c5ed757438d3a5d79bd36fd789ef0297ce70))
|
|
||||||
* upgrade primeng from 15.0.0-rc.1 to 15.4.1 ([#4962](https://github.com/Ombi-app/Ombi/issues/4962)) [skip ci] ([23a4fed](https://github.com/Ombi-app/Ombi/commit/23a4fede69898a25b342aed78a8cda553c1fd18d))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -122,10 +122,10 @@ Here are some of the features Ombi has:
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/MattJeanes">
|
<a href="https://github.com/AmyJeanes">
|
||||||
<img src="https://avatars.githubusercontent.com/u/2363642?v=4" width="50;" alt="MattJeanes"/>
|
<img src="https://avatars.githubusercontent.com/u/2363642?v=4" width="50;" alt="AmyJeanes"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Matt Jeanes</b></sub>
|
<sub><b>Amy Jeanes</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
|
|
|
@ -29,5 +29,6 @@ namespace Ombi.Api.Plex
|
||||||
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
|
Task<PlexAddWrapper> AddUser(string emailAddress, string serverId, string authToken, int[] libs);
|
||||||
Task<PlexWatchlistContainer> GetWatchlist(string plexToken, CancellationToken cancellationToken);
|
Task<PlexWatchlistContainer> GetWatchlist(string plexToken, CancellationToken cancellationToken);
|
||||||
Task<PlexWatchlistMetadataContainer> GetWatchlistMetadata(string ratingKey, string plexToken, CancellationToken cancellationToken);
|
Task<PlexWatchlistMetadataContainer> GetWatchlistMetadata(string ratingKey, string plexToken, CancellationToken cancellationToken);
|
||||||
|
Task<bool> Ping(string authToken, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -68,7 +68,7 @@ namespace Ombi.Api.Plex
|
||||||
private const string FriendsUri = "https://plex.tv/api/users";
|
private const string FriendsUri = "https://plex.tv/api/users";
|
||||||
private const string GetAccountUri = "https://plex.tv/users/account.json";
|
private const string GetAccountUri = "https://plex.tv/users/account.json";
|
||||||
private const string ServerUri = "https://plex.tv/pms/servers.xml";
|
private const string ServerUri = "https://plex.tv/pms/servers.xml";
|
||||||
private const string WatchlistUri = "https://metadata.provider.plex.tv/";
|
private const string WatchlistUri = "https://discover.provider.plex.tv/";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sign into the Plex API
|
/// Sign into the Plex API
|
||||||
|
@ -320,6 +320,30 @@ namespace Ombi.Api.Plex
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Pings the Plex API to validate if a token is still valid
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authToken">The authentication token to validate</param>
|
||||||
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
|
/// <returns>True if the token is valid, false otherwise</returns>
|
||||||
|
public async Task<bool> Ping(string authToken, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var request = new Request("api/v2/ping", "https://plex.tv/", HttpMethod.Get);
|
||||||
|
await AddHeaders(request, authToken);
|
||||||
|
|
||||||
|
// We don't need to parse the response, just check if the request succeeds
|
||||||
|
await Api.Request(request, cancellationToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// If the request fails (401, 403, etc.), the token is invalid
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the required headers and also the authorization header
|
/// Adds the required headers and also the authorization header
|
||||||
|
|
52
src/Ombi.Core/Authentication/PlexTokenKeepAliveService.cs
Normal file
52
src/Ombi.Core/Authentication/PlexTokenKeepAliveService.cs
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ombi.Api.Plex;
|
||||||
|
|
||||||
|
namespace Ombi.Core.Authentication
|
||||||
|
{
|
||||||
|
public interface IPlexTokenKeepAliveService
|
||||||
|
{
|
||||||
|
Task<bool> KeepTokenAliveAsync(string token, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlexTokenKeepAliveService : IPlexTokenKeepAliveService
|
||||||
|
{
|
||||||
|
private readonly IPlexApi _plexApi;
|
||||||
|
private readonly ILogger<PlexTokenKeepAliveService> _logger;
|
||||||
|
|
||||||
|
public PlexTokenKeepAliveService(IPlexApi plexApi, ILogger<PlexTokenKeepAliveService> logger)
|
||||||
|
{
|
||||||
|
_plexApi = plexApi;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> KeepTokenAliveAsync(string token, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Token is null or empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the Ping method to validate the token
|
||||||
|
var isValid = await _plexApi.Ping(token, cancellationToken);
|
||||||
|
|
||||||
|
if (!isValid)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Token validation failed - token may be expired or invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error occurred while keeping token alive");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -598,13 +598,13 @@ namespace Ombi.Core.Engine
|
||||||
|
|
||||||
public async Task<RequestEngineResult> ApproveMovieById(int requestId, bool is4K)
|
public async Task<RequestEngineResult> ApproveMovieById(int requestId, bool is4K)
|
||||||
{
|
{
|
||||||
var request = await MovieRepository.Find(requestId);
|
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||||
return await ApproveMovie(request, is4K);
|
return await ApproveMovie(request, is4K);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason, bool is4K)
|
public async Task<RequestEngineResult> DenyMovieById(int modelId, string denyReason, bool is4K)
|
||||||
{
|
{
|
||||||
var request = await MovieRepository.Find(modelId);
|
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == modelId);
|
||||||
if (request == null)
|
if (request == null)
|
||||||
{
|
{
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
|
@ -790,7 +790,7 @@ namespace Ombi.Core.Engine
|
||||||
|
|
||||||
public async Task<RequestEngineResult> ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken)
|
public async Task<RequestEngineResult> ReProcessRequest(int requestId, bool is4K, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var request = await MovieRepository.Find(requestId);
|
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == requestId);
|
||||||
if (request == null)
|
if (request == null)
|
||||||
{
|
{
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
|
@ -805,7 +805,7 @@ namespace Ombi.Core.Engine
|
||||||
|
|
||||||
public async Task<RequestEngineResult> MarkUnavailable(int modelId, bool is4K)
|
public async Task<RequestEngineResult> MarkUnavailable(int modelId, bool is4K)
|
||||||
{
|
{
|
||||||
var request = await MovieRepository.Find(modelId);
|
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == modelId);
|
||||||
if (request == null)
|
if (request == null)
|
||||||
{
|
{
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
|
@ -834,7 +834,7 @@ namespace Ombi.Core.Engine
|
||||||
|
|
||||||
public async Task<RequestEngineResult> MarkAvailable(int modelId, bool is4K)
|
public async Task<RequestEngineResult> MarkAvailable(int modelId, bool is4K)
|
||||||
{
|
{
|
||||||
var request = await MovieRepository.Find(modelId);
|
var request = await MovieRepository.GetWithUser().FirstOrDefaultAsync(x => x.Id == modelId);
|
||||||
if (request == null)
|
if (request == null)
|
||||||
{
|
{
|
||||||
return new RequestEngineResult
|
return new RequestEngineResult
|
||||||
|
|
|
@ -182,7 +182,10 @@ namespace Ombi.Core.Senders
|
||||||
if (settings.SendUserTags)
|
if (settings.SendUserTags)
|
||||||
{
|
{
|
||||||
var userTag = await GetOrCreateTag(model, settings);
|
var userTag = await GetOrCreateTag(model, settings);
|
||||||
tags.Add(userTag.id);
|
if (userTag != null)
|
||||||
|
{
|
||||||
|
tags.Add(userTag.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overrides on the request take priority
|
// Overrides on the request take priority
|
||||||
|
@ -198,7 +201,9 @@ namespace Ombi.Core.Senders
|
||||||
List<MovieResponse> movies;
|
List<MovieResponse> movies;
|
||||||
// Check if the movie already exists? Since it could be unmonitored
|
// Check if the movie already exists? Since it could be unmonitored
|
||||||
|
|
||||||
movies = await _radarrV3Api.GetMovies(settings.ApiKey, settings.FullUri);
|
// Get the appropriate Radarr instance settings for existence check
|
||||||
|
var existenceCheckSettings = is4k ? await _radarr4KSettings.GetSettingsAsync() : settings;
|
||||||
|
movies = await _radarrV3Api.GetMovies(existenceCheckSettings.ApiKey, existenceCheckSettings.FullUri);
|
||||||
|
|
||||||
var existingMovie = movies.FirstOrDefault(x => x.tmdbId == model.TheMovieDbId);
|
var existingMovie = movies.FirstOrDefault(x => x.tmdbId == model.TheMovieDbId);
|
||||||
if (existingMovie == null)
|
if (existingMovie == null)
|
||||||
|
@ -246,6 +251,12 @@ namespace Ombi.Core.Senders
|
||||||
|
|
||||||
private async Task<Tag> GetOrCreateTag(MovieRequests model, RadarrSettings s)
|
private async Task<Tag> GetOrCreateTag(MovieRequests model, RadarrSettings s)
|
||||||
{
|
{
|
||||||
|
if (model.RequestedUser == null)
|
||||||
|
{
|
||||||
|
_log.LogWarning("Cannot create tag - RequestedUser is null for movie request {MovieTitle}", model.Title);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var tagName = model.RequestedUser.UserName;
|
var tagName = model.RequestedUser.UserName;
|
||||||
// Does tag exist?
|
// Does tag exist?
|
||||||
|
|
||||||
|
|
|
@ -133,7 +133,14 @@ namespace Ombi.Core.Senders
|
||||||
string seriesType;
|
string seriesType;
|
||||||
int? tagToUse = null;
|
int? tagToUse = null;
|
||||||
|
|
||||||
|
Logger.LogInformation("Starting SendToSonarr for series {Title} (TvDbId: {TvDbId})", model.ParentRequest.Title, model.ParentRequest.TvDbId);
|
||||||
|
Logger.LogInformation("Series type: {SeriesType}", model.SeriesType);
|
||||||
|
|
||||||
var profiles = await UserQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId);
|
var profiles = await UserQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId);
|
||||||
|
if (profiles != null)
|
||||||
|
{
|
||||||
|
Logger.LogInformation("Found user quality profile for user {UserId}", model.RequestedUserId);
|
||||||
|
}
|
||||||
|
|
||||||
if (model.SeriesType == SeriesType.Anime)
|
if (model.SeriesType == SeriesType.Anime)
|
||||||
{
|
{
|
||||||
|
@ -141,8 +148,10 @@ namespace Ombi.Core.Senders
|
||||||
// For some reason, if we haven't got one use the first root folder in Sonarr
|
// For some reason, if we haven't got one use the first root folder in Sonarr
|
||||||
if (!int.TryParse(s.RootPathAnime, out int animePath))
|
if (!int.TryParse(s.RootPathAnime, out int animePath))
|
||||||
{
|
{
|
||||||
|
Logger.LogWarning("Failed to parse RootPathAnime: {RootPathAnime}, falling back to main root path", s.RootPathAnime);
|
||||||
animePath = int.Parse(s.RootPath); // Set it to the main root folder if we have no anime folder.
|
animePath = int.Parse(s.RootPath); // Set it to the main root folder if we have no anime folder.
|
||||||
}
|
}
|
||||||
|
Logger.LogInformation("Using anime path ID: {AnimePath}", animePath);
|
||||||
rootFolderPath = await GetSonarrRootPath(animePath, s);
|
rootFolderPath = await GetSonarrRootPath(animePath, s);
|
||||||
languageProfileId = s.LanguageProfileAnime > 0 ? s.LanguageProfileAnime : s.LanguageProfile;
|
languageProfileId = s.LanguageProfileAnime > 0 ? s.LanguageProfileAnime : s.LanguageProfile;
|
||||||
|
|
||||||
|
@ -154,6 +163,7 @@ namespace Ombi.Core.Senders
|
||||||
{
|
{
|
||||||
if (profiles.SonarrRootPathAnime > 0)
|
if (profiles.SonarrRootPathAnime > 0)
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation("Using user's anime root path override: {RootPath}", profiles.SonarrRootPathAnime);
|
||||||
rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPathAnime, s);
|
rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPathAnime, s);
|
||||||
}
|
}
|
||||||
if (profiles.SonarrQualityProfileAnime > 0)
|
if (profiles.SonarrQualityProfileAnime > 0)
|
||||||
|
@ -169,11 +179,13 @@ namespace Ombi.Core.Senders
|
||||||
int.TryParse(s.QualityProfile, out qualityToUse);
|
int.TryParse(s.QualityProfile, out qualityToUse);
|
||||||
// Get the root path from the rootfolder selected.
|
// Get the root path from the rootfolder selected.
|
||||||
// For some reason, if we haven't got one use the first root folder in Sonarr
|
// For some reason, if we haven't got one use the first root folder in Sonarr
|
||||||
|
Logger.LogInformation("Using standard path ID: {RootPath}", s.RootPath);
|
||||||
rootFolderPath = await GetSonarrRootPath(int.Parse(s.RootPath), s);
|
rootFolderPath = await GetSonarrRootPath(int.Parse(s.RootPath), s);
|
||||||
if (profiles != null)
|
if (profiles != null)
|
||||||
{
|
{
|
||||||
if (profiles.SonarrRootPath > 0)
|
if (profiles.SonarrRootPath > 0)
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation("Using user's standard root path override: {RootPath}", profiles.SonarrRootPath);
|
||||||
rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPath, s);
|
rootFolderPath = await GetSonarrRootPath(profiles.SonarrRootPath, s);
|
||||||
}
|
}
|
||||||
if (profiles.SonarrQualityProfile > 0)
|
if (profiles.SonarrQualityProfile > 0)
|
||||||
|
@ -193,6 +205,7 @@ namespace Ombi.Core.Senders
|
||||||
|
|
||||||
if (model.ParentRequest.RootFolder.HasValue && model.ParentRequest.RootFolder.Value > 0)
|
if (model.ParentRequest.RootFolder.HasValue && model.ParentRequest.RootFolder.Value > 0)
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation("Using request root folder override: {RootFolder}", model.ParentRequest.RootFolder.Value);
|
||||||
rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder.Value, s);
|
rootFolderPath = await GetSonarrRootPath(model.ParentRequest.RootFolder.Value, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,6 +214,8 @@ namespace Ombi.Core.Senders
|
||||||
languageProfileId = model.ParentRequest.LanguageProfile.Value;
|
languageProfileId = model.ParentRequest.LanguageProfile.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Final root folder path: {RootFolderPath}", rootFolderPath);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (tagToUse.HasValue)
|
if (tagToUse.HasValue)
|
||||||
|
@ -520,17 +535,36 @@ namespace Ombi.Core.Senders
|
||||||
|
|
||||||
private async Task<string> GetSonarrRootPath(int pathId, SonarrSettings sonarrSettings)
|
private async Task<string> GetSonarrRootPath(int pathId, SonarrSettings sonarrSettings)
|
||||||
{
|
{
|
||||||
|
Logger.LogInformation("Getting Sonarr root path for ID: {PathId}", pathId);
|
||||||
var rootFoldersResult = await SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
var rootFoldersResult = await SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri);
|
||||||
|
|
||||||
|
if (rootFoldersResult == null || !rootFoldersResult.Any())
|
||||||
|
{
|
||||||
|
Logger.LogError("No root folders returned from Sonarr API");
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Found {Count} root folders in Sonarr", rootFoldersResult.Count());
|
||||||
|
foreach (var folder in rootFoldersResult)
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Root folder - ID: {Id}, Path: {Path}", folder.id, folder.path);
|
||||||
|
}
|
||||||
|
|
||||||
if (pathId == 0)
|
if (pathId == 0)
|
||||||
{
|
{
|
||||||
return rootFoldersResult.FirstOrDefault().path;
|
var defaultPath = rootFoldersResult.FirstOrDefault()?.path;
|
||||||
|
Logger.LogInformation("Using first root folder as default: {Path}", defaultPath);
|
||||||
|
return defaultPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var r in rootFoldersResult?.Where(r => r.id == pathId))
|
var matchingFolder = rootFoldersResult.FirstOrDefault(r => r.id == pathId);
|
||||||
|
if (matchingFolder != null)
|
||||||
{
|
{
|
||||||
return r.path;
|
Logger.LogInformation("Found matching root folder for ID {PathId}: {Path}", pathId, matchingFolder.path);
|
||||||
|
return matchingFolder.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.LogError("No matching root folder found for ID: {PathId}", pathId);
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,7 @@ namespace Ombi.DependencyInjection
|
||||||
services.AddTransient<IMusicSender, MusicSender>();
|
services.AddTransient<IMusicSender, MusicSender>();
|
||||||
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
services.AddTransient<IMassEmailSender, MassEmailSender>();
|
||||||
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
services.AddTransient<IPlexOAuthManager, PlexOAuthManager>();
|
||||||
|
services.AddTransient<IPlexTokenKeepAliveService, PlexTokenKeepAliveService>();
|
||||||
services.AddTransient<IVoteEngine, VoteEngine>();
|
services.AddTransient<IVoteEngine, VoteEngine>();
|
||||||
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
|
services.AddTransient<IDemoMovieSearchEngine, DemoMovieSearchEngine>();
|
||||||
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
|
services.AddTransient<IDemoTvSearchEngine, DemoTvSearchEngine>();
|
||||||
|
|
|
@ -24,6 +24,7 @@ using Ombi.Notifications.Models;
|
||||||
using Ombi.Core.Notifications;
|
using Ombi.Core.Notifications;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Core;
|
using Ombi.Core;
|
||||||
|
using Ombi.Core.Authentication;
|
||||||
|
|
||||||
namespace Ombi.Schedule.Tests
|
namespace Ombi.Schedule.Tests
|
||||||
{
|
{
|
||||||
|
@ -43,6 +44,8 @@ namespace Ombi.Schedule.Tests
|
||||||
_mocker.Use(um);
|
_mocker.Use(um);
|
||||||
_context = _mocker.GetMock<IJobExecutionContext>();
|
_context = _mocker.GetMock<IJobExecutionContext>();
|
||||||
_context.Setup(x => x.CancellationToken).Returns(CancellationToken.None);
|
_context.Setup(x => x.CancellationToken).Returns(CancellationToken.None);
|
||||||
|
// Mock the keep-alive service to return true by default
|
||||||
|
_mocker.Use<IPlexTokenKeepAliveService>(Mock.Of<IPlexTokenKeepAliveService>(s => s.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()) == Task.FromResult(true)));
|
||||||
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||||
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>().AsQueryable().BuildMock());
|
_mocker.Setup<IRepository<PlexWatchlistUserError>, IQueryable<PlexWatchlistUserError>>(x => x.GetAll()).Returns(new List<PlexWatchlistUserError>().AsQueryable().BuildMock());
|
||||||
_mocker.Setup<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()));
|
_mocker.Setup<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()));
|
||||||
|
@ -838,5 +841,43 @@ namespace Ombi.Schedule.Tests
|
||||||
// Assert
|
// Assert
|
||||||
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Never);
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Never);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task SkipsUserIfTokenKeepAliveFails()
|
||||||
|
{
|
||||||
|
// Arrange: Set up the keep-alive service to return false (token invalid/expired)
|
||||||
|
var keepAliveMock = new Mock<IPlexTokenKeepAliveService>();
|
||||||
|
keepAliveMock.Setup(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(false);
|
||||||
|
_mocker.Use(keepAliveMock.Object);
|
||||||
|
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
|
// Act
|
||||||
|
await _subject.Execute(_context.Object);
|
||||||
|
// Assert: Should not attempt to import watchlist if keep-alive fails
|
||||||
|
keepAliveMock.Verify(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||||
|
_mocker.Verify<IPlexApi>(x => x.GetWatchlist(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
_mocker.Verify<INotificationHelper>(x => x.Notify(It.IsAny<NotificationOptions>()), Times.Never); // or Times.Once if notification is expected
|
||||||
|
}
|
||||||
|
[Test]
|
||||||
|
public async Task CallsKeepAliveForEachPlexUser()
|
||||||
|
{
|
||||||
|
// Arrange: Multiple Plex users
|
||||||
|
var users = new List<OmbiUser>
|
||||||
|
{
|
||||||
|
new OmbiUser { Id = "abc1", UserType = UserType.PlexUser, MediaServerToken = "abc1", UserName = "abc1", NormalizedUserName = "ABC1" },
|
||||||
|
new OmbiUser { Id = "abc2", UserType = UserType.PlexUser, MediaServerToken = "abc2", UserName = "abc2", NormalizedUserName = "ABC2" },
|
||||||
|
};
|
||||||
|
var um = MockHelper.MockUserManager(users);
|
||||||
|
_mocker.Use(um);
|
||||||
|
var keepAliveMock = new Mock<IPlexTokenKeepAliveService>();
|
||||||
|
keepAliveMock.Setup(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>())).ReturnsAsync(true);
|
||||||
|
_mocker.Use(keepAliveMock.Object);
|
||||||
|
_subject = _mocker.CreateInstance<PlexWatchlistImport>();
|
||||||
|
_mocker.Setup<ISettingsService<PlexSettings>, Task<PlexSettings>>(x => x.GetSettingsAsync()).ReturnsAsync(new PlexSettings { Enable = true, EnableWatchlistImport = true });
|
||||||
|
// Act
|
||||||
|
await _subject.Execute(_context.Object);
|
||||||
|
// Assert: KeepAlive should be called for each user
|
||||||
|
keepAliveMock.Verify(x => x.KeepTokenAliveAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Exactly(users.Count));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,11 +43,12 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
private readonly IRepository<PlexWatchlistUserError> _userError;
|
private readonly IRepository<PlexWatchlistUserError> _userError;
|
||||||
private readonly IMovieDbApi _movieDbApi;
|
private readonly IMovieDbApi _movieDbApi;
|
||||||
private readonly INotificationHelper _notificationHelper;
|
private readonly INotificationHelper _notificationHelper;
|
||||||
|
private readonly IPlexTokenKeepAliveService _tokenKeepAliveService;
|
||||||
|
|
||||||
public PlexWatchlistImport(IPlexApi plexApi, ISettingsService<PlexSettings> settings, OmbiUserManager ombiUserManager,
|
public PlexWatchlistImport(IPlexApi plexApi, ISettingsService<PlexSettings> settings, OmbiUserManager ombiUserManager,
|
||||||
IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, INotificationHubService notificationHubService,
|
IMovieRequestEngine movieRequestEngine, ITvRequestEngine tvRequestEngine, INotificationHubService notificationHubService,
|
||||||
ILogger<PlexWatchlistImport> logger, IExternalRepository<PlexWatchlistHistory> watchlistRepo, IRepository<PlexWatchlistUserError> userError,
|
ILogger<PlexWatchlistImport> logger, IExternalRepository<PlexWatchlistHistory> watchlistRepo, IRepository<PlexWatchlistUserError> userError,
|
||||||
IMovieDbApi movieDbApi, INotificationHelper notificationHelper)
|
IMovieDbApi movieDbApi, INotificationHelper notificationHelper, IPlexTokenKeepAliveService tokenKeepAliveService)
|
||||||
{
|
{
|
||||||
_plexApi = plexApi;
|
_plexApi = plexApi;
|
||||||
_settings = settings;
|
_settings = settings;
|
||||||
|
@ -60,6 +61,7 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
_userError = userError;
|
_userError = userError;
|
||||||
_movieDbApi = movieDbApi;
|
_movieDbApi = movieDbApi;
|
||||||
_notificationHelper = notificationHelper;
|
_notificationHelper = notificationHelper;
|
||||||
|
_tokenKeepAliveService = tokenKeepAliveService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Execute(IJobExecutionContext context)
|
public async Task Execute(IJobExecutionContext context)
|
||||||
|
@ -97,6 +99,36 @@ namespace Ombi.Schedule.Jobs.Plex
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug($"Starting Watchlist Import for {user.UserName} with token {user.MediaServerToken}");
|
_logger.LogDebug($"Starting Watchlist Import for {user.UserName} with token {user.MediaServerToken}");
|
||||||
|
|
||||||
|
// Keep the token alive before attempting watchlist import
|
||||||
|
var keepAliveSuccess = await _tokenKeepAliveService.KeepTokenAliveAsync(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
|
||||||
|
if (!keepAliveSuccess)
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"Token for user '{user.UserName}' is invalid or expired (keep-alive failed). Recording error and skipping.");
|
||||||
|
await _userError.Add(new PlexWatchlistUserError
|
||||||
|
{
|
||||||
|
UserId = user.Id,
|
||||||
|
MediaServerToken = user.MediaServerToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send notification to user about token expiration
|
||||||
|
if (settings.NotifyOnWatchlistTokenExpiration && !string.IsNullOrEmpty(user.Email))
|
||||||
|
{
|
||||||
|
var notificationModel = new NotificationOptions
|
||||||
|
{
|
||||||
|
NotificationType = NotificationType.PlexWatchlistTokenExpired,
|
||||||
|
Recipient = user.Email,
|
||||||
|
DateTime = DateTime.Now,
|
||||||
|
Substitutes = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "UserName", user.UserName }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await _notificationHelper.Notify(notificationModel);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var watchlist = await _plexApi.GetWatchlist(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
|
var watchlist = await _plexApi.GetWatchlist(user.MediaServerToken, context?.CancellationToken ?? CancellationToken.None);
|
||||||
if (watchlist?.AuthError ?? false)
|
if (watchlist?.AuthError ?? false)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,13 +5,17 @@
|
||||||
<mat-button-toggle id="{{id}}Tv" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Tv}" value="{{DiscoverOption.Tv}}" class="discover-filter-button">{{'Discovery.Tv' | translate}}</mat-button-toggle>
|
<mat-button-toggle id="{{id}}Tv" [ngClass]="{'button-active': discoverOptions === DiscoverOption.Tv}" value="{{DiscoverOption.Tv}}" class="discover-filter-button">{{'Discovery.Tv' | translate}}</mat-button-toggle>
|
||||||
</mat-button-toggle-group>
|
</mat-button-toggle-group>
|
||||||
</div>
|
</div>
|
||||||
@defer (when discoverResults.length > 0) {
|
@defer (when discoverResults.length > 0; prefetch on idle) {
|
||||||
<p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()">
|
<p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="discoverResults" [responsiveOptions]="responsiveOptions" (onPage)="newPage()">
|
||||||
<ng-template let-result pTemplate="item">
|
<ng-template let-result pTemplate="item">
|
||||||
<discover-card [discoverType]="discoverType" [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
|
<discover-card [discoverType]="discoverType" [isAdmin]="isAdmin" [result]="result" [is4kEnabled]="is4kEnabled"></discover-card>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-carousel>
|
</p-carousel>
|
||||||
}
|
}
|
||||||
@placeholder(minimum 500) {
|
@placeholder(minimum 300) {
|
||||||
<p-skeleton width="100%" height="18rem"></p-skeleton>
|
<div class="row loading-container">
|
||||||
|
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
|
@ -105,6 +105,30 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container .col-2 {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: calc(10% - 9px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.loading-container .col-2 {
|
||||||
|
width: calc(50% - 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.loading-container .col-2 {
|
||||||
|
width: calc(100% - 0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width:755px){
|
@media (min-width:755px){
|
||||||
::ng-deep .p-carousel-item{
|
::ng-deep .p-carousel-item{
|
||||||
flex: 1 0 200px !important;
|
flex: 1 0 200px !important;
|
||||||
|
|
|
@ -43,7 +43,7 @@ export class CarouselListComponent implements OnInit {
|
||||||
get mediaTypeStorageKey() {
|
get mediaTypeStorageKey() {
|
||||||
return "DiscoverOptions" + this.discoverType.toString();
|
return "DiscoverOptions" + this.discoverType.toString();
|
||||||
};
|
};
|
||||||
private amountToLoad = 17;
|
private amountToLoad = 10;
|
||||||
private currentlyLoaded = 0;
|
private currentlyLoaded = 0;
|
||||||
private baseUrl: string = "";
|
private baseUrl: string = "";
|
||||||
|
|
||||||
|
@ -148,6 +148,7 @@ export class CarouselListComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ngOnInit() {
|
public async ngOnInit() {
|
||||||
|
|
||||||
this.is4kEnabled = this.featureFacade.is4kEnabled();
|
this.is4kEnabled = this.featureFacade.is4kEnabled();
|
||||||
this.currentlyLoaded = 0;
|
this.currentlyLoaded = 0;
|
||||||
const localDiscoverOptions = +this.storageService.get(this.mediaTypeStorageKey);
|
const localDiscoverOptions = +this.storageService.get(this.mediaTypeStorageKey);
|
||||||
|
@ -155,11 +156,15 @@ export class CarouselListComponent implements OnInit {
|
||||||
this.discoverOptions = DiscoverOption[DiscoverOption[localDiscoverOptions]];
|
this.discoverOptions = DiscoverOption[DiscoverOption[localDiscoverOptions]];
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentIteration = 0;
|
// Load initial data - just enough to fill the first carousel page
|
||||||
while (this.discoverResults.length <= 14 && currentIteration <= 3) {
|
// This reduces initial API calls and improves loading performance
|
||||||
currentIteration++;
|
await this.loadData(false);
|
||||||
|
|
||||||
|
// If we don't have enough results to fill the carousel, load one more batch
|
||||||
|
if (this.discoverResults.length < 10) {
|
||||||
await this.loadData(false);
|
await this.loadData(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async toggleChanged(event: MatButtonToggleChange) {
|
public async toggleChanged(event: MatButtonToggleChange) {
|
||||||
|
|
|
@ -1,46 +1,108 @@
|
||||||
<div class="small-middle-container">
|
<div class="small-middle-container">
|
||||||
<div class="section">
|
@defer (on viewport; prefetch on idle) {
|
||||||
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2>
|
<div class="section">
|
||||||
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
|
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2>
|
||||||
</div>
|
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
|
||||||
<div class="section">
|
|
||||||
<h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
|
|
||||||
<div>
|
|
||||||
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
} @placeholder(minimum 300) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.Genres' | translate }}</h2>
|
||||||
<div class="section" [hidden]="!showSeasonal">
|
<p-skeleton width="100%" height="60px"></p-skeleton>
|
||||||
<h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
|
|
||||||
<div>
|
|
||||||
<carousel-list
|
|
||||||
[id]="'seasonal'"
|
|
||||||
[isAdmin]="isAdmin"
|
|
||||||
[discoverType]="DiscoverType.Seasonal"
|
|
||||||
(movieCount)="setSeasonalMovieCount($event)"
|
|
||||||
></carousel-list>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
|
||||||
<div class="section">
|
@defer (on viewport; prefetch on idle) {
|
||||||
<h2>{{ 'Discovery.PopularTab' | translate }}</h2>
|
<div class="section">
|
||||||
<div>
|
<h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
|
||||||
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list>
|
<div>
|
||||||
|
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
} @placeholder(minimum 300) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
|
||||||
|
<div class="row loading-container">
|
||||||
|
<div class="col-2" *ngFor="let item of [1,2,3,4,5]">
|
||||||
|
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="section">
|
@defer (on viewport; prefetch on idle) {
|
||||||
<h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
|
<div class="section" [hidden]="!showSeasonal">
|
||||||
<div>
|
<h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
|
||||||
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list>
|
<div>
|
||||||
|
<carousel-list
|
||||||
|
[id]="'seasonal'"
|
||||||
|
[isAdmin]="isAdmin"
|
||||||
|
[discoverType]="DiscoverType.Seasonal"
|
||||||
|
(movieCount)="setSeasonalMovieCount($event)"
|
||||||
|
></carousel-list>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
} @placeholder(minimum 300) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
|
||||||
|
<div class="row loading-container">
|
||||||
|
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="section">
|
@defer (on viewport; prefetch on idle) {
|
||||||
<h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
|
<div class="section">
|
||||||
<div>
|
<h2>{{ 'Discovery.PopularTab' | translate }}</h2>
|
||||||
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
<div>
|
||||||
|
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
} @placeholder(minimum 300) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.PopularTab' | translate }}</h2>
|
||||||
|
<div class="row loading-container">
|
||||||
|
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (on viewport; prefetch on idle) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
|
||||||
|
<div>
|
||||||
|
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @placeholder(minimum 300) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
|
||||||
|
<div class="row loading-container">
|
||||||
|
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@defer (on viewport; prefetch on idle) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
|
||||||
|
<div>
|
||||||
|
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @placeholder(minimum 300) {
|
||||||
|
<div class="section">
|
||||||
|
<h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
|
||||||
|
<div class="row loading-container">
|
||||||
|
<div class="col-2" *ngFor="let item of [1,2,3,4,5,6,7,8,9,10]">
|
||||||
|
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,4 +9,28 @@ h2{
|
||||||
margin-top:40px;
|
margin-top:40px;
|
||||||
margin-left:40px;
|
margin-left:40px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container .col-2 {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: calc(10% - 9px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.loading-container .col-2 {
|
||||||
|
width: calc(50% - 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.loading-container .col-2 {
|
||||||
|
width: calc(100% - 0px);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
@defer (when requests()) {
|
@defer (when requests(); prefetch on idle) {
|
||||||
<div *ngIf="requests().length > 0">
|
<div *ngIf="requests().length > 0">
|
||||||
<p-carousel #carousel [value]="requests()" [numVisible]="3" [numScroll]="1"
|
<p-carousel #carousel [value]="requests()" [numVisible]="3" [numScroll]="1"
|
||||||
[responsiveOptions]="responsiveOptions" [page]="0">
|
[responsiveOptions]="responsiveOptions" [page]="0">
|
||||||
|
@ -13,21 +13,9 @@
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</p-carousel>
|
</p-carousel>
|
||||||
</div>
|
</div>
|
||||||
}@placeholder(minimum 500) {
|
}@placeholder(minimum 300) {
|
||||||
<div class="row loading-container">
|
<div class="row loading-container">
|
||||||
<div class="col-2">
|
<div class="col-2" *ngFor="let item of [1,2,3,4,5]">
|
||||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<p-skeleton width="100%" height="270px"></p-skeleton>
|
<p-skeleton width="100%" height="270px"></p-skeleton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -105,12 +105,32 @@
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 0 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-container .col-2 {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: calc(20% - 8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.loading-container .col-2 {
|
||||||
|
width: calc(50% - 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.loading-container .col-2 {
|
||||||
|
width: calc(100% - 0px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width:755px){
|
@media (min-width:755px){
|
||||||
::ng-deep .p-carousel-item{
|
::ng-deep .p-carousel-item{
|
||||||
flex: 1 0 200px !important;
|
flex: 1 0 200px !important;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
margin-left: 10rem;
|
|
||||||
}
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
import { Pipe, PipeTransform } from "@angular/core";
|
import { Pipe, PipeTransform } from "@angular/core";
|
||||||
import { FormatPipe } from 'ngx-date-fns';
|
import { FormatPipe } from 'ngx-date-fns';
|
||||||
|
import { parseISO, format } from 'date-fns';
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: "ombiDate",
|
name: "ombiDate",
|
||||||
|
@ -10,8 +11,16 @@ export class OmbiDatePipe implements PipeTransform {
|
||||||
private FormatPipe: FormatPipe,
|
private FormatPipe: FormatPipe,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public transform(value: string, format: string ) {
|
public transform(value: string, formatStr: string ) {
|
||||||
const date = new Date(value);
|
if (!value) {
|
||||||
return this.FormatPipe.transform(date, format);
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the ISO string as UTC
|
||||||
|
const utcDate = parseISO(value);
|
||||||
|
|
||||||
|
// Format the date using date-fns format function
|
||||||
|
// This will automatically handle the UTC to local conversion
|
||||||
|
return format(utcDate, formatStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,13 @@ export class UserManagementUserComponent implements OnInit {
|
||||||
private appUrl: string = this.customizationFacade.appUrl();
|
private appUrl: string = this.customizationFacade.appUrl();
|
||||||
private accessToken: string;
|
private accessToken: string;
|
||||||
|
|
||||||
|
// List of excluded notification agents that should not be shown in user preferences
|
||||||
|
private readonly excludedAgents = [
|
||||||
|
INotificationAgent.Email,
|
||||||
|
INotificationAgent.Mobile,
|
||||||
|
INotificationAgent.Webhook
|
||||||
|
];
|
||||||
|
|
||||||
constructor(private identityService: IdentityService,
|
constructor(private identityService: IdentityService,
|
||||||
private notificationService: MessageService,
|
private notificationService: MessageService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
|
@ -74,9 +81,15 @@ export class UserManagementUserComponent implements OnInit {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(this.edit) {
|
if(this.edit) {
|
||||||
this.identityService.getNotificationPreferencesForUser(this.userId).subscribe(x => this.notificationPreferences = x);
|
this.identityService.getNotificationPreferencesForUser(this.userId).subscribe(x => {
|
||||||
|
// Filter out excluded notification agents
|
||||||
|
this.notificationPreferences = x.filter(pref => !this.excludedAgents.includes(pref.agent));
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.identityService.getNotificationPreferences().subscribe(x => this.notificationPreferences = x);
|
this.identityService.getNotificationPreferences().subscribe(x => {
|
||||||
|
// Filter out excluded notification agents
|
||||||
|
this.notificationPreferences = x.filter(pref => !this.excludedAgents.includes(pref.agent));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.sonarrService.getQualityProfilesWithoutSettings().subscribe(x => {
|
this.sonarrService.getQualityProfilesWithoutSettings().subscribe(x => {
|
||||||
this.sonarrQualities = x;
|
this.sonarrQualities = x;
|
||||||
|
|
|
@ -40,7 +40,6 @@ namespace Ombi.Controllers.V1
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Settings Controller
|
/// The Settings Controller
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Admin]
|
|
||||||
[ApiV1]
|
[ApiV1]
|
||||||
[Produces("application/json")]
|
[Produces("application/json")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
|
@ -78,6 +77,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Ombi settings.
|
/// Gets the Ombi settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("ombi")]
|
[HttpGet("ombi")]
|
||||||
public async Task<OmbiSettings> OmbiSettings()
|
public async Task<OmbiSettings> OmbiSettings()
|
||||||
{
|
{
|
||||||
|
@ -110,6 +110,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="ombi">The ombi.</param>
|
/// <param name="ombi">The ombi.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("ombi")]
|
[HttpPost("ombi")]
|
||||||
public async Task<bool> OmbiSettings([FromBody]OmbiSettings ombi)
|
public async Task<bool> OmbiSettings([FromBody]OmbiSettings ombi)
|
||||||
{
|
{
|
||||||
|
@ -145,6 +146,7 @@ namespace Ombi.Controllers.V1
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Admin]
|
||||||
[HttpPost("ombi/resetApi")]
|
[HttpPost("ombi/resetApi")]
|
||||||
public async Task<string> ResetApiKey()
|
public async Task<string> ResetApiKey()
|
||||||
{
|
{
|
||||||
|
@ -159,6 +161,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Plex Settings.
|
/// Gets the Plex Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("plex")]
|
[HttpGet("plex")]
|
||||||
public async Task<PlexSettings> PlexSettings()
|
public async Task<PlexSettings> PlexSettings()
|
||||||
{
|
{
|
||||||
|
@ -185,6 +188,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="plex">The plex.</param>
|
/// <param name="plex">The plex.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("plex")]
|
[HttpPost("plex")]
|
||||||
public async Task<bool> PlexSettings([FromBody]PlexSettings plex)
|
public async Task<bool> PlexSettings([FromBody]PlexSettings plex)
|
||||||
{
|
{
|
||||||
|
@ -207,6 +211,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Emby Settings.
|
/// Gets the Emby Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("emby")]
|
[HttpGet("emby")]
|
||||||
public async Task<EmbySettings> EmbySettings()
|
public async Task<EmbySettings> EmbySettings()
|
||||||
{
|
{
|
||||||
|
@ -218,6 +223,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="emby">The emby.</param>
|
/// <param name="emby">The emby.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("emby")]
|
[HttpPost("emby")]
|
||||||
public async Task<bool> EmbySettings([FromBody]EmbySettings emby)
|
public async Task<bool> EmbySettings([FromBody]EmbySettings emby)
|
||||||
{
|
{
|
||||||
|
@ -243,6 +249,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Jellyfin Settings.
|
/// Gets the Jellyfin Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("jellyfin")]
|
[HttpGet("jellyfin")]
|
||||||
public async Task<JellyfinSettings> JellyfinSettings()
|
public async Task<JellyfinSettings> JellyfinSettings()
|
||||||
{
|
{
|
||||||
|
@ -254,6 +261,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="jellyfin">The jellyfin.</param>
|
/// <param name="jellyfin">The jellyfin.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("jellyfin")]
|
[HttpPost("jellyfin")]
|
||||||
public async Task<bool> JellyfinSettings([FromBody]JellyfinSettings jellyfin)
|
public async Task<bool> JellyfinSettings([FromBody]JellyfinSettings jellyfin)
|
||||||
{
|
{
|
||||||
|
@ -291,6 +299,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("landingpage")]
|
[HttpPost("landingpage")]
|
||||||
public async Task<bool> LandingPageSettings([FromBody]LandingPageSettings settings)
|
public async Task<bool> LandingPageSettings([FromBody]LandingPageSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -326,6 +335,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("customization")]
|
[HttpPost("customization")]
|
||||||
public async Task<bool> CustomizationSettings([FromBody]CustomizationSettings settings)
|
public async Task<bool> CustomizationSettings([FromBody]CustomizationSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -344,6 +354,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Get's the preset themes available
|
/// Get's the preset themes available
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("themes")]
|
[HttpGet("themes")]
|
||||||
public async Task<IEnumerable<PresetThemeViewModel>> GetThemes()
|
public async Task<IEnumerable<PresetThemeViewModel>> GetThemes()
|
||||||
{
|
{
|
||||||
|
@ -389,6 +400,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("sonarr")]
|
[HttpPost("sonarr")]
|
||||||
|
[Admin]
|
||||||
public async Task<bool> SonarrSettings([FromBody]SonarrSettings settings)
|
public async Task<bool> SonarrSettings([FromBody]SonarrSettings settings)
|
||||||
{
|
{
|
||||||
var result = await Save(settings);
|
var result = await Save(settings);
|
||||||
|
@ -418,6 +430,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Lidarr Settings.
|
/// Gets the Lidarr Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("lidarr")]
|
[HttpGet("lidarr")]
|
||||||
public async Task<LidarrSettings> LidarrSettings()
|
public async Task<LidarrSettings> LidarrSettings()
|
||||||
{
|
{
|
||||||
|
@ -441,6 +454,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("lidarr")]
|
[HttpPost("lidarr")]
|
||||||
public async Task<bool> LidarrSettings([FromBody]LidarrSettings settings)
|
public async Task<bool> LidarrSettings([FromBody]LidarrSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -457,6 +471,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("authentication")]
|
[HttpPost("authentication")]
|
||||||
public async Task<bool> AuthenticationsSettings([FromBody]AuthenticationSettings settings)
|
public async Task<bool> AuthenticationsSettings([FromBody]AuthenticationSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -479,6 +494,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("radarr")]
|
[HttpPost("radarr")]
|
||||||
public async Task<bool> RadarrSettings([FromBody]RadarrCombinedModel settings)
|
public async Task<bool> RadarrSettings([FromBody]RadarrCombinedModel settings)
|
||||||
{
|
{
|
||||||
|
@ -500,6 +516,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("Update")]
|
[HttpPost("Update")]
|
||||||
public async Task<bool> UpdateSettings([FromBody]UpdateSettings settings)
|
public async Task<bool> UpdateSettings([FromBody]UpdateSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -510,6 +527,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the UserManagement Settings.
|
/// Gets the UserManagement Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("UserManagement")]
|
[HttpGet("UserManagement")]
|
||||||
public async Task<UserManagementSettings> UserManagementSettings()
|
public async Task<UserManagementSettings> UserManagementSettings()
|
||||||
{
|
{
|
||||||
|
@ -521,6 +539,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("UserManagement")]
|
[HttpPost("UserManagement")]
|
||||||
public async Task<bool> UserManagementSettings([FromBody]UserManagementSettings settings)
|
public async Task<bool> UserManagementSettings([FromBody]UserManagementSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -531,6 +550,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Update Settings.
|
/// Gets the Update Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("Update")]
|
[HttpGet("Update")]
|
||||||
public async Task<UpdateSettings> UpdateSettings()
|
public async Task<UpdateSettings> UpdateSettings()
|
||||||
{
|
{
|
||||||
|
@ -543,6 +563,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the CouchPotatoSettings Settings.
|
/// Gets the CouchPotatoSettings Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("CouchPotato")]
|
[HttpGet("CouchPotato")]
|
||||||
public async Task<CouchPotatoSettings> CouchPotatoSettings()
|
public async Task<CouchPotatoSettings> CouchPotatoSettings()
|
||||||
{
|
{
|
||||||
|
@ -554,6 +575,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("CouchPotato")]
|
[HttpPost("CouchPotato")]
|
||||||
public async Task<bool> CouchPotatoSettings([FromBody]CouchPotatoSettings settings)
|
public async Task<bool> CouchPotatoSettings([FromBody]CouchPotatoSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -564,6 +586,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the DogNzbSettings Settings.
|
/// Gets the DogNzbSettings Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("DogNzb")]
|
[HttpGet("DogNzb")]
|
||||||
public async Task<DogNzbSettings> DogNzbSettings()
|
public async Task<DogNzbSettings> DogNzbSettings()
|
||||||
{
|
{
|
||||||
|
@ -575,6 +598,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("DogNzb")]
|
[HttpPost("DogNzb")]
|
||||||
public async Task<bool> DogNzbSettings([FromBody]DogNzbSettings settings)
|
public async Task<bool> DogNzbSettings([FromBody]DogNzbSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -586,6 +610,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("SickRage")]
|
[HttpPost("SickRage")]
|
||||||
public async Task<bool> SickRageSettings([FromBody]SickRageSettings settings)
|
public async Task<bool> SickRageSettings([FromBody]SickRageSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -596,6 +621,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the SickRage Settings.
|
/// Gets the SickRage Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("SickRage")]
|
[HttpGet("SickRage")]
|
||||||
public async Task<SickRageSettings> SickRageSettings()
|
public async Task<SickRageSettings> SickRageSettings()
|
||||||
{
|
{
|
||||||
|
@ -606,6 +632,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the JobSettings Settings.
|
/// Gets the JobSettings Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("jobs")]
|
[HttpGet("jobs")]
|
||||||
public async Task<JobSettings> JobSettings()
|
public async Task<JobSettings> JobSettings()
|
||||||
{
|
{
|
||||||
|
@ -638,6 +665,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("jobs")]
|
[HttpPost("jobs")]
|
||||||
public async Task<JobSettingsViewModel> JobSettings([FromBody]JobSettings settings)
|
public async Task<JobSettingsViewModel> JobSettings([FromBody]JobSettings settings)
|
||||||
{
|
{
|
||||||
|
@ -681,6 +709,7 @@ namespace Ombi.Controllers.V1
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("testcron")]
|
[HttpPost("testcron")]
|
||||||
|
[Admin]
|
||||||
public CronTestModel TestCron([FromBody] CronViewModelBody body)
|
public CronTestModel TestCron([FromBody] CronViewModelBody body)
|
||||||
{
|
{
|
||||||
var model = new CronTestModel();
|
var model = new CronTestModel();
|
||||||
|
@ -714,6 +743,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("Issues")]
|
[HttpPost("Issues")]
|
||||||
|
[Admin]
|
||||||
public async Task<bool> IssueSettings([FromBody]IssueSettings settings)
|
public async Task<bool> IssueSettings([FromBody]IssueSettings settings)
|
||||||
{
|
{
|
||||||
return await Save(settings);
|
return await Save(settings);
|
||||||
|
@ -744,6 +774,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpPost("vote")]
|
[HttpPost("vote")]
|
||||||
|
[Admin]
|
||||||
public async Task<bool> VoteSettings([FromBody]VoteSettings settings)
|
public async Task<bool> VoteSettings([FromBody]VoteSettings settings)
|
||||||
{
|
{
|
||||||
return await Save(settings);
|
return await Save(settings);
|
||||||
|
@ -754,6 +785,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet("vote")]
|
[HttpGet("vote")]
|
||||||
|
[Admin]
|
||||||
public async Task<VoteSettings> VoteSettings()
|
public async Task<VoteSettings> VoteSettings()
|
||||||
{
|
{
|
||||||
return await Get<VoteSettings>();
|
return await Get<VoteSettings>();
|
||||||
|
@ -772,6 +804,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The settings.</param>
|
/// <param name="settings">The settings.</param>
|
||||||
[HttpPost("themoviedb")]
|
[HttpPost("themoviedb")]
|
||||||
|
[Admin]
|
||||||
public async Task<bool> TheMovieDbSettings([FromBody]TheMovieDbSettings settings)
|
public async Task<bool> TheMovieDbSettings([FromBody]TheMovieDbSettings settings)
|
||||||
{
|
{
|
||||||
return await Save(settings);
|
return await Save(settings);
|
||||||
|
@ -780,6 +813,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get The Movie DB settings.
|
/// Get The Movie DB settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Admin]
|
||||||
[HttpGet("themoviedb")]
|
[HttpGet("themoviedb")]
|
||||||
public async Task<TheMovieDbSettings> TheMovieDbSettings()
|
public async Task<TheMovieDbSettings> TheMovieDbSettings()
|
||||||
{
|
{
|
||||||
|
@ -791,6 +825,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/email")]
|
[HttpPost("notifications/email")]
|
||||||
public async Task<bool> EmailNotificationSettings([FromBody] EmailNotificationsViewModel model)
|
public async Task<bool> EmailNotificationSettings([FromBody] EmailNotificationsViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -808,6 +843,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Email Notification Settings.
|
/// Gets the Email Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/email")]
|
[HttpGet("notifications/email")]
|
||||||
public async Task<EmailNotificationsViewModel> EmailNotificationSettings()
|
public async Task<EmailNotificationsViewModel> EmailNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -838,6 +874,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/discord")]
|
[HttpPost("notifications/discord")]
|
||||||
public async Task<bool> DiscordNotificationSettings([FromBody] DiscordNotificationsViewModel model)
|
public async Task<bool> DiscordNotificationSettings([FromBody] DiscordNotificationsViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -855,6 +892,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the discord Notification Settings.
|
/// Gets the discord Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/discord")]
|
[HttpGet("notifications/discord")]
|
||||||
public async Task<DiscordNotificationsViewModel> DiscordNotificationSettings()
|
public async Task<DiscordNotificationsViewModel> DiscordNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -873,6 +911,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/telegram")]
|
[HttpPost("notifications/telegram")]
|
||||||
public async Task<bool> TelegramNotificationSettings([FromBody] TelegramNotificationsViewModel model)
|
public async Task<bool> TelegramNotificationSettings([FromBody] TelegramNotificationsViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -890,6 +929,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the telegram Notification Settings.
|
/// Gets the telegram Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/telegram")]
|
[HttpGet("notifications/telegram")]
|
||||||
public async Task<TelegramNotificationsViewModel> TelegramNotificationSettings()
|
public async Task<TelegramNotificationsViewModel> TelegramNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -907,6 +947,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/pushbullet")]
|
[HttpPost("notifications/pushbullet")]
|
||||||
public async Task<bool> PushbulletNotificationSettings([FromBody] PushbulletNotificationViewModel model)
|
public async Task<bool> PushbulletNotificationSettings([FromBody] PushbulletNotificationViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -924,6 +965,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the pushbullet Notification Settings.
|
/// Gets the pushbullet Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/pushbullet")]
|
[HttpGet("notifications/pushbullet")]
|
||||||
public async Task<PushbulletNotificationViewModel> PushbulletNotificationSettings()
|
public async Task<PushbulletNotificationViewModel> PushbulletNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -941,6 +983,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/pushover")]
|
[HttpPost("notifications/pushover")]
|
||||||
public async Task<bool> PushoverNotificationSettings([FromBody] PushoverNotificationViewModel model)
|
public async Task<bool> PushoverNotificationSettings([FromBody] PushoverNotificationViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -958,6 +1001,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the pushover Notification Settings.
|
/// Gets the pushover Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/pushover")]
|
[HttpGet("notifications/pushover")]
|
||||||
public async Task<PushoverNotificationViewModel> PushoverNotificationSettings()
|
public async Task<PushoverNotificationViewModel> PushoverNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -976,6 +1020,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/slack")]
|
[HttpPost("notifications/slack")]
|
||||||
public async Task<bool> SlacktNotificationSettings([FromBody] SlackNotificationsViewModel model)
|
public async Task<bool> SlacktNotificationSettings([FromBody] SlackNotificationsViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -993,6 +1038,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the slack Notification Settings.
|
/// Gets the slack Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/slack")]
|
[HttpGet("notifications/slack")]
|
||||||
public async Task<SlackNotificationsViewModel> SlackNotificationSettings()
|
public async Task<SlackNotificationsViewModel> SlackNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -1010,6 +1056,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/mattermost")]
|
[HttpPost("notifications/mattermost")]
|
||||||
public async Task<bool> MattermostNotificationSettings([FromBody] MattermostNotificationsViewModel model)
|
public async Task<bool> MattermostNotificationSettings([FromBody] MattermostNotificationsViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -1027,6 +1074,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Mattermost Notification Settings.
|
/// Gets the Mattermost Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/mattermost")]
|
[HttpGet("notifications/mattermost")]
|
||||||
public async Task<MattermostNotificationsViewModel> MattermostNotificationSettings()
|
public async Task<MattermostNotificationsViewModel> MattermostNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -1043,6 +1091,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Twilio Notification Settings.
|
/// Gets the Twilio Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/twilio")]
|
[HttpGet("notifications/twilio")]
|
||||||
public async Task<TwilioSettingsViewModel> TwilioNotificationSettings()
|
public async Task<TwilioSettingsViewModel> TwilioNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -1064,6 +1113,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/twilio")]
|
[HttpPost("notifications/twilio")]
|
||||||
public async Task<bool> TwilioNotificationSettings([FromBody] TwilioSettingsViewModel model)
|
public async Task<bool> TwilioNotificationSettings([FromBody] TwilioSettingsViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -1082,6 +1132,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/mobile")]
|
[HttpPost("notifications/mobile")]
|
||||||
public async Task<bool> MobileNotificationSettings([FromBody] MobileNotificationsViewModel model)
|
public async Task<bool> MobileNotificationSettings([FromBody] MobileNotificationsViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -1099,6 +1150,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Mobile Notification Settings.
|
/// Gets the Mobile Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/mobile")]
|
[HttpGet("notifications/mobile")]
|
||||||
public async Task<MobileNotificationsViewModel> MobileNotificationSettings()
|
public async Task<MobileNotificationsViewModel> MobileNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -1116,6 +1168,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/gotify")]
|
[HttpPost("notifications/gotify")]
|
||||||
public async Task<bool> GotifyNotificationSettings([FromBody] GotifyNotificationViewModel model)
|
public async Task<bool> GotifyNotificationSettings([FromBody] GotifyNotificationViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -1133,6 +1186,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the gotify Notification Settings.
|
/// Gets the gotify Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/gotify")]
|
[HttpGet("notifications/gotify")]
|
||||||
public async Task<GotifyNotificationViewModel> GotifyNotificationSettings()
|
public async Task<GotifyNotificationViewModel> GotifyNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -1150,6 +1204,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/webhook")]
|
[HttpPost("notifications/webhook")]
|
||||||
public async Task<bool> WebhookNotificationSettings([FromBody] WebhookNotificationViewModel model)
|
public async Task<bool> WebhookNotificationSettings([FromBody] WebhookNotificationViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -1163,6 +1218,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the webhook notification settings.
|
/// Gets the webhook notification settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/webhook")]
|
[HttpGet("notifications/webhook")]
|
||||||
public async Task<WebhookNotificationViewModel> WebhookNotificationSettings()
|
public async Task<WebhookNotificationViewModel> WebhookNotificationSettings()
|
||||||
{
|
{
|
||||||
|
@ -1177,6 +1233,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model.</param>
|
/// <param name="model">The model.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/newsletter")]
|
[HttpPost("notifications/newsletter")]
|
||||||
public async Task<bool> NewsletterSettings([FromBody] NewsletterNotificationViewModel model)
|
public async Task<bool> NewsletterSettings([FromBody] NewsletterNotificationViewModel model)
|
||||||
{
|
{
|
||||||
|
@ -1191,6 +1248,7 @@ namespace Ombi.Controllers.V1
|
||||||
}
|
}
|
||||||
|
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
[Admin]
|
||||||
[HttpPost("notifications/newsletterdatabase")]
|
[HttpPost("notifications/newsletterdatabase")]
|
||||||
public async Task<bool> UpdateNewsletterDatabase()
|
public async Task<bool> UpdateNewsletterDatabase()
|
||||||
{
|
{
|
||||||
|
@ -1201,6 +1259,7 @@ namespace Ombi.Controllers.V1
|
||||||
/// Gets the Newsletter Notification Settings.
|
/// Gets the Newsletter Notification Settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
[Admin]
|
||||||
[HttpGet("notifications/newsletter")]
|
[HttpGet("notifications/newsletter")]
|
||||||
public async Task<NewsletterNotificationViewModel> NewsletterSettings()
|
public async Task<NewsletterNotificationViewModel> NewsletterSettings()
|
||||||
{
|
{
|
||||||
|
|
|
@ -159,7 +159,7 @@
|
||||||
"RequestedBy": "Sol·licitat per",
|
"RequestedBy": "Sol·licitat per",
|
||||||
"Status": "Estat",
|
"Status": "Estat",
|
||||||
"RequestStatus": "Estat de la sol·licitud",
|
"RequestStatus": "Estat de la sol·licitud",
|
||||||
"Watched": "Watched",
|
"Watched": "Vist",
|
||||||
"WatchedTooltip": "The user who made the request has watched it",
|
"WatchedTooltip": "The user who made the request has watched it",
|
||||||
"WatchedProgressTooltip": "Shows how much the user who made the request has watched it",
|
"WatchedProgressTooltip": "Shows how much the user who made the request has watched it",
|
||||||
"WatchedByUsersCount": "{{count}} users have watched this.",
|
"WatchedByUsersCount": "{{count}} users have watched this.",
|
||||||
|
@ -408,7 +408,7 @@
|
||||||
"Movies": "Pel·lícules",
|
"Movies": "Pel·lícules",
|
||||||
"Combined": "Combinat",
|
"Combined": "Combinat",
|
||||||
"Tv": "TV",
|
"Tv": "TV",
|
||||||
"Genres": "Genres",
|
"Genres": "Gèneres",
|
||||||
"CardDetails": {
|
"CardDetails": {
|
||||||
"Availability": "Disponibilitat",
|
"Availability": "Disponibilitat",
|
||||||
"Studio": "Estudi",
|
"Studio": "Estudi",
|
||||||
|
|
|
@ -159,10 +159,10 @@
|
||||||
"RequestedBy": "Verzocht Door",
|
"RequestedBy": "Verzocht Door",
|
||||||
"Status": "Status",
|
"Status": "Status",
|
||||||
"RequestStatus": "Aanvraagstatus",
|
"RequestStatus": "Aanvraagstatus",
|
||||||
"Watched": "Watched",
|
"Watched": "Bekeken",
|
||||||
"WatchedTooltip": "The user who made the request has watched it",
|
"WatchedTooltip": "De gebruiker die het verzoek heeft ingediend, heeft het bekeken",
|
||||||
"WatchedProgressTooltip": "Shows how much the user who made the request has watched it",
|
"WatchedProgressTooltip": "Laat zien hoeveel de gebruiker die het verzoek heeft gemaakt het heeft bekeken",
|
||||||
"WatchedByUsersCount": "{{count}} users have watched this.",
|
"WatchedByUsersCount": "{{count}} gebruikers hebben dit bekeken.",
|
||||||
"Denied": " Geweigerd:",
|
"Denied": " Geweigerd:",
|
||||||
"TheatricalRelease": "Cinema Uitgave: {{date}}",
|
"TheatricalRelease": "Cinema Uitgave: {{date}}",
|
||||||
"ReleaseDate": "Uitgekomen: {{date}}",
|
"ReleaseDate": "Uitgekomen: {{date}}",
|
||||||
|
@ -225,7 +225,7 @@
|
||||||
"Denied": "Geselecteerde items succesvol afgekeurd"
|
"Denied": "Geselecteerde items succesvol afgekeurd"
|
||||||
},
|
},
|
||||||
"SuccessfullyApproved": "Succesvol goedgekeurd",
|
"SuccessfullyApproved": "Succesvol goedgekeurd",
|
||||||
"SuccessfullyDenied": "Successfully Denied",
|
"SuccessfullyDenied": "Succesvol Geweigerd",
|
||||||
"SuccessfullyDeleted": "Verzoek succesvol verwijderd",
|
"SuccessfullyDeleted": "Verzoek succesvol verwijderd",
|
||||||
"NowAvailable": "Verzoek is nu beschikbaar",
|
"NowAvailable": "Verzoek is nu beschikbaar",
|
||||||
"NowUnavailable": "Verzoek is nu niet beschikbaar",
|
"NowUnavailable": "Verzoek is nu niet beschikbaar",
|
||||||
|
@ -241,7 +241,7 @@
|
||||||
"NoPermissionsOnBehalf": "Je hebt niet de juiste rechten om namens gebruikers aan te vragen!",
|
"NoPermissionsOnBehalf": "Je hebt niet de juiste rechten om namens gebruikers aan te vragen!",
|
||||||
"NoPermissions": "Je hebt de juiste rechten niet!",
|
"NoPermissions": "Je hebt de juiste rechten niet!",
|
||||||
"RequestDoesNotExist": "Verzoek bestaat niet",
|
"RequestDoesNotExist": "Verzoek bestaat niet",
|
||||||
"ChildRequestDoesNotExist": "Child Request does not exist",
|
"ChildRequestDoesNotExist": "Kindverzoek bestaat niet",
|
||||||
"NoPermissionsRequestMovie": "Je bent niet gemachtigd om een film aan te vragen",
|
"NoPermissionsRequestMovie": "Je bent niet gemachtigd om een film aan te vragen",
|
||||||
"NoPermissionsRequestTV": "Je bent niet gemachtigd om een serie aan te vragen",
|
"NoPermissionsRequestTV": "Je bent niet gemachtigd om een serie aan te vragen",
|
||||||
"NoPermissionsRequestAlbum": "Je bent niet gemachtigd om een album aan te vragen",
|
"NoPermissionsRequestAlbum": "Je bent niet gemachtigd om een album aan te vragen",
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"version": "4.48.0"
|
"version": "4.49.3"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue