pull/3895/head
tidusjar 5 years ago
commit 8febad53b0

@ -59,6 +59,7 @@ We integrate with the following applications:
Supported notifications:
* SMTP Notifications (Email)
* Discord
* Gotify
* Slack
* Pushbullet
* Pushover

@ -3,7 +3,7 @@
#addin "Cake.Gulp"
#addin "SharpZipLib"
#addin nuget:?package=Cake.Compression&version=0.1.4
#addin "Cake.Incubator"
#addin "Cake.Incubator&version=3.1.0"
#addin "Cake.Yarn"
//////////////////////////////////////////////////////////////////////

@ -0,0 +1,36 @@
using System.Net.Http;
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public class GotifyApi : IGotifyApi
{
public GotifyApi(IApi api)
{
_api = api;
}
private readonly IApi _api;
public async Task PushAsync(string baseUrl, string accessToken, string subject, string body, sbyte priority)
{
var request = new Request("/message", baseUrl, HttpMethod.Post);
request.AddQueryString("token", accessToken);
request.AddHeader("Access-Token", accessToken);
request.ApplicationJsonContentType();
var jsonBody = new
{
message = body,
title = subject,
priority = priority
};
request.AddJsonBody(jsonBody);
await _api.Request(request);
}
}
}

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace Ombi.Api.Gotify
{
public interface IGotifyApi
{
Task PushAsync(string endpoint, string accessToken, string subject, string body, sbyte priority);
}
}

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyVersion>3.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<Version></Version>
<PackageVersion></PackageVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ombi.Api\Ombi.Api.csproj" />
</ItemGroup>
</Project>

@ -4,6 +4,7 @@ using Moq;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Store.Entities.Requests;
using NUnit.Framework;
using Ombi.Core.Authentication;
using Ombi.Helpers;
namespace Ombi.Core.Tests.Rule.Request
@ -16,7 +17,7 @@ namespace Ombi.Core.Tests.Rule.Request
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new AutoApproveRule(PrincipalMock.Object);
Rule = new AutoApproveRule(PrincipalMock.Object, null);
}

@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Moq;
using NUnit.Framework;
using Ombi.Core.Rule.Rules;
using Ombi.Core.Rule.Rules.Request;
using Ombi.Helpers;
using Ombi.Store.Entities.Requests;
@ -15,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Request
{
PrincipalMock = new Mock<IPrincipal>();
Rule = new CanRequestRule(PrincipalMock.Object);
Rule = new CanRequestRule(PrincipalMock.Object, null);
}

@ -16,7 +16,7 @@ namespace Ombi.Core.Tests.Rule.Search
public void Setup()
{
ContextMock = new Mock<IEmbyContentRepository>();
Rule = new EmbyAvailabilityRule(ContextMock.Object);
Rule = new EmbyAvailabilityRule(ContextMock.Object, null);
}
private EmbyAvailabilityRule Rule { get; set; }

@ -32,14 +32,13 @@ namespace Ombi.Core.Engine
{
public TvRequestEngine(ITvMazeApi tvApi, IMovieDbApi movApi, IRequestServiceMain requestService, IPrincipal user,
INotificationHelper helper, IRuleEvaluator rule, OmbiUserManager manager,
ITvSender sender, IAuditRepository audit, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
ITvSender sender, IRepository<RequestLog> rl, ISettingsService<OmbiSettings> settings, ICacheService cache,
IRepository<RequestSubscription> sub) : base(user, requestService, rule, manager, cache, settings, sub)
{
TvApi = tvApi;
MovieDbApi = movApi;
NotificationHelper = helper;
TvSender = sender;
Audit = audit;
_requestLog = rl;
}
@ -47,7 +46,6 @@ namespace Ombi.Core.Engine
private ITvMazeApi TvApi { get; }
private IMovieDbApi MovieDbApi { get; }
private ITvSender TvSender { get; }
private IAuditRepository Audit { get; }
private readonly IRepository<RequestLog> _requestLog;
public async Task<RequestEngineResult> RequestTvShow(TvRequestViewModel tv)
@ -85,8 +83,6 @@ namespace Ombi.Core.Engine
}
}
await Audit.Record(AuditType.Added, AuditArea.TvRequest, $"Added Request {tvBuilder.ChildRequest.Title}", Username);
var existingRequest = await TvRepository.Get().FirstOrDefaultAsync(x => x.TvDbId == tv.TvDbId);
if (existingRequest != null)
{
@ -408,7 +404,6 @@ namespace Ombi.Core.Engine
public async Task<TvRequests> UpdateTvRequest(TvRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
var allRequests = TvRepository.Get();
var results = await allRequests.FirstOrDefaultAsync(x => x.Id == request.Id);
@ -451,7 +446,6 @@ namespace Ombi.Core.Engine
if (request.Approved)
{
NotificationHelper.Notify(request, NotificationType.RequestApproved);
await Audit.Record(AuditType.Approved, AuditArea.TvRequest, $"Approved Request {request.Title}", Username);
// Autosend
await TvSender.Send(request);
}
@ -483,9 +477,7 @@ namespace Ombi.Core.Engine
public async Task<ChildRequests> UpdateChildRequest(ChildRequests request)
{
await Audit.Record(AuditType.Updated, AuditArea.TvRequest, $"Updated Request {request.Title}", Username);
await TvRepository.UpdateChild(request);
await TvRepository.UpdateChild(request);
return request;
}
@ -503,16 +495,14 @@ namespace Ombi.Core.Engine
// Delete the parent
TvRepository.Db.TvRequests.Remove(parent);
}
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Db.SaveChangesAsync();
}
public async Task RemoveTvRequest(int requestId)
{
var request = await TvRepository.Get().FirstOrDefaultAsync(x => x.Id == requestId);
await Audit.Record(AuditType.Deleted, AuditArea.TvRequest, $"Deleting Request {request.Title}", Username);
await TvRepository.Delete(request);
await TvRepository.Delete(request);
}
public async Task<bool> UserHasRequest(string userId)

@ -0,0 +1,23 @@

using System.Collections.Generic;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
namespace Ombi.Core.Models.UI
{
/// <summary>
/// The view model for the notification settings page
/// </summary>
/// <seealso cref="GotifyNotificationSettings" />
public class GotifyNotificationViewModel : GotifySettings
{
/// <summary>
/// Gets or sets the notification templates.
/// </summary>
/// <value>
/// The notification templates.
/// </value>
public List<NotificationTemplates> NotificationTemplates { get; set; }
}
}

@ -1,5 +1,7 @@
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Models.Requests;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
@ -10,28 +12,31 @@ namespace Ombi.Core.Rule.Rules.Request
{
public class AutoApproveRule : BaseRequestRule, IRules<BaseRequest>
{
public AutoApproveRule(IPrincipal principal)
public AutoApproveRule(IPrincipal principal, OmbiUserManager um)
{
User = principal;
_manager = um;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
{
obj.Approved = true;
return Task.FromResult(Success());
return Success();
}
if (obj.RequestType == RequestType.Movie && User.IsInRole(OmbiRoles.AutoApproveMovie))
if (obj.RequestType == RequestType.Movie && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
obj.Approved = true;
if (obj.RequestType == RequestType.TvShow && User.IsInRole(OmbiRoles.AutoApproveTv))
if (obj.RequestType == RequestType.TvShow && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
obj.Approved = true;
if (obj.RequestType == RequestType.Album && User.IsInRole(OmbiRoles.AutoApproveMusic))
if (obj.RequestType == RequestType.Album && await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
obj.Approved = true;
return Task.FromResult(Success()); // We don't really care, we just don't set the obj to approve
return Success(); // We don't really care, we just don't set the obj to approve
}
}
}

@ -1,46 +1,52 @@
using Ombi.Store.Entities;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Authentication;
using Ombi.Core.Rule.Interfaces;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
namespace Ombi.Core.Rule.Rules
namespace Ombi.Core.Rule.Rules.Request
{
public class CanRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public CanRequestRule(IPrincipal principal)
public CanRequestRule(IPrincipal principal, OmbiUserManager manager)
{
User = principal;
_manager = manager;
}
private IPrincipal User { get; }
private readonly OmbiUserManager _manager;
public Task<RuleResult> Execute(BaseRequest obj)
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (User.IsInRole(OmbiRoles.Admin))
return Task.FromResult(Success());
var user = await _manager.Users.FirstOrDefaultAsync(x => x.UserName == User.Identity.Name);
if (await _manager.IsInRoleAsync(user, OmbiRoles.Admin))
return Success();
if (obj.RequestType == RequestType.Movie)
{
if (User.IsInRole(OmbiRoles.RequestMovie) || User.IsInRole(OmbiRoles.AutoApproveMovie))
return Task.FromResult(Success());
return Task.FromResult(Fail("You do not have permissions to Request a Movie"));
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMovie) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMovie))
return Success();
return Fail("You do not have permissions to Request a Movie");
}
if (obj.RequestType == RequestType.TvShow)
{
if (User.IsInRole(OmbiRoles.RequestTv) || User.IsInRole(OmbiRoles.AutoApproveTv))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestTv) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveTv))
return Success();
}
if (obj.RequestType == RequestType.Album)
{
if (User.IsInRole(OmbiRoles.RequestMusic) || User.IsInRole(OmbiRoles.AutoApproveMusic))
return Task.FromResult(Success());
if (await _manager.IsInRoleAsync(user, OmbiRoles.RequestMusic) || await _manager.IsInRoleAsync(user, OmbiRoles.AutoApproveMusic))
return Success();
}
return Task.FromResult(Fail("You do not have permissions to Request a TV Show"));
return Fail("You do not have permissions to Request a TV Show");
}
}
}

@ -3,6 +3,8 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Models.Search;
using Ombi.Core.Rule.Interfaces;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
@ -11,12 +13,14 @@ namespace Ombi.Core.Rule.Rules.Search
{
public class EmbyAvailabilityRule : BaseSearchRule, IRules<SearchViewModel>
{
public EmbyAvailabilityRule(IEmbyContentRepository repo)
public EmbyAvailabilityRule(IEmbyContentRepository repo, ISettingsService<EmbySettings> s)
{
EmbyContentRepository = repo;
EmbySettings = s;
}
private IEmbyContentRepository EmbyContentRepository { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
public async Task<RuleResult> Execute(SearchViewModel obj)
{
@ -60,7 +64,16 @@ namespace Ombi.Core.Rule.Rules.Search
if (item != null)
{
obj.Available = true;
obj.EmbyUrl = item.Url;
var s = await EmbySettings.GetSettingsAsync();
var server = s.Servers.FirstOrDefault(x => x.ServerHostname != null);
if ((server?.ServerHostname ?? string.Empty).HasValue())
{
obj.EmbyUrl = $"{server.ServerHostname}#!/itemdetails.html?id={item.EmbyId}";
}
else
{
obj.EmbyUrl = $"https://app.emby.media/#!/itemdetails.html?id={item.EmbyId}";
}
if (obj.Type == RequestType.TvShow)
{

@ -32,6 +32,7 @@ using Ombi.Api.CouchPotato;
using Ombi.Api.DogNzb;
using Ombi.Api.FanartTv;
using Ombi.Api.Github;
using Ombi.Api.Gotify;
using Ombi.Api.Lidarr;
using Ombi.Api.Mattermost;
using Ombi.Api.Notifications;
@ -128,6 +129,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IOmbiService, OmbiService>();
services.AddTransient<IFanartTvApi, FanartTvApi>();
services.AddTransient<IPushoverApi, PushoverApi>();
services.AddTransient<IGotifyApi, GotifyApi>();
services.AddTransient<IMattermostApi, MattermostApi>();
services.AddTransient<ICouchPotatoApi, CouchPotatoApi>();
services.AddTransient<IDogNzbApi, DogNzbApi>();
@ -140,28 +142,28 @@ namespace Ombi.DependencyInjection
}
public static void RegisterStore(this IServiceCollection services) {
services.AddEntityFrameworkSqlite().AddDbContext<OmbiContext>();
services.AddEntityFrameworkSqlite().AddDbContext<SettingsContext>();
services.AddEntityFrameworkSqlite().AddDbContext<ExternalContext>();
services.AddDbContext<OmbiContext>();
services.AddDbContext<SettingsContext>();
services.AddDbContext<ExternalContext>();
services.AddScoped<IOmbiContext, OmbiContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<ISettingsContext, SettingsContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddScoped<IExternalContext, ExternalContext>(); // https://docs.microsoft.com/en-us/aspnet/core/data/entity-framework-6
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
services.AddTransient<ISettingsResolver, SettingsResolver>();
services.AddTransient<IPlexContentRepository, PlexServerContentRepository>();
services.AddTransient<IEmbyContentRepository, EmbyContentRepository>();
services.AddTransient<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddScoped<ISettingsRepository, SettingsJsonRepository>();
services.AddScoped<ISettingsResolver, SettingsResolver>();
services.AddScoped<IPlexContentRepository, PlexServerContentRepository>();
services.AddScoped<IEmbyContentRepository, EmbyContentRepository>();
services.AddScoped<INotificationTemplatesRepository, NotificationTemplatesRepository>();
services.AddTransient<ITvRequestRepository, TvRequestRepository>();
services.AddTransient<IMovieRequestRepository, MovieRequestRepository>();
services.AddTransient<IMusicRequestRepository, MusicRequestRepository>();
services.AddTransient<IAuditRepository, AuditRepository>();
services.AddTransient<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddTransient<ITokenRepository, TokenRepository>();
services.AddTransient(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
services.AddTransient(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
services.AddScoped<ITvRequestRepository, TvRequestRepository>();
services.AddScoped<IMovieRequestRepository, MovieRequestRepository>();
services.AddScoped<IMusicRequestRepository, MusicRequestRepository>();
services.AddScoped<IAuditRepository, AuditRepository>();
services.AddScoped<IApplicationConfigRepository, ApplicationConfigRepository>();
services.AddScoped<ITokenRepository, TokenRepository>();
services.AddScoped(typeof(ISettingsService<>), typeof(SettingsService<>));
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
services.AddScoped(typeof(IExternalRepository<>), typeof(ExternalRepository<>));
}
public static void RegisterServices(this IServiceCollection services)
{
@ -169,7 +171,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<INotificationService, NotificationService>();
services.AddTransient<IEmailProvider, GenericEmailProvider>();
services.AddTransient<INotificationHelper, NotificationHelper>();
services.AddTransient<ICacheService, CacheService>();
services.AddSingleton<ICacheService, CacheService>();
services.AddTransient<IDiscordNotification, DiscordNotification>();
services.AddTransient<IEmailNotification, EmailNotification>();
@ -178,6 +180,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ISlackNotification, SlackNotification>();
services.AddTransient<IMattermostNotification, MattermostNotification>();
services.AddTransient<IPushoverNotification, PushoverNotification>();
services.AddTransient<IGotifyNotification, GotifyNotification>();
services.AddTransient<ITelegramNotification, TelegramNotification>();
services.AddTransient<IMobileNotification, MobileNotification>();
services.AddTransient<IChangeLogProcessor, ChangeLogProcessor>();

@ -32,6 +32,7 @@ namespace Ombi.Helpers
public static EventId MattermostNotification => new EventId(4004);
public static EventId PushoverNotification => new EventId(4005);
public static EventId TelegramNotifcation => new EventId(4006);
public static EventId GotifyNotification => new EventId(4007);
public static EventId TvSender => new EventId(5000);
public static EventId SonarrSender => new EventId(5001);

@ -10,5 +10,6 @@
Slack = 5,
Mattermost = 6,
Mobile = 7,
Gotify = 8,
}
}

@ -19,6 +19,7 @@ namespace Ombi.Mapping.Profiles
CreateMap<UpdateSettingsViewModel, UpdateSettings>().ReverseMap();
CreateMap<MobileNotificationsViewModel, MobileNotificationSettings>().ReverseMap();
CreateMap<NewsletterNotificationViewModel, NewsletterSettings>().ReverseMap();
CreateMap<GotifyNotificationViewModel, GotifySettings>().ReverseMap();
}
}
}

@ -0,0 +1,116 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Ombi.Api.Gotify;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
namespace Ombi.Notifications.Agents
{
public class GotifyNotification : BaseNotification<GotifySettings>, IGotifyNotification
{
public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref)
{
Api = api;
Logger = log;
}
public override string NotificationName => "GotifyNotification";
private IGotifyApi Api { get; }
private ILogger<GotifyNotification> Logger { get; }
protected override bool ValidateConfiguration(GotifySettings settings)
{
return settings.Enabled && !string.IsNullOrEmpty(settings.BaseUrl) && !string.IsNullOrEmpty(settings.ApplicationToken);
}
protected override async Task NewRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.NewRequest);
}
protected override async Task NewIssue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.Issue);
}
protected override async Task IssueComment(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueComment);
}
protected override async Task IssueResolved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.IssueResolved);
}
protected override async Task AddedToRequestQueue(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.ItemAddedToFaultQueue);
}
protected override async Task RequestDeclined(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestDeclined);
}
protected override async Task RequestApproved(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestApproved);
}
protected override async Task AvailableRequest(NotificationOptions model, GotifySettings settings)
{
await Run(model, settings, NotificationType.RequestAvailable);
}
protected override async Task Send(NotificationMessage model, GotifySettings settings)
{
try
{
await Api.PushAsync(settings.BaseUrl, settings.ApplicationToken, model.Subject, model.Message, settings.Priority);
}
catch (Exception e)
{
Logger.LogError(LoggingEvents.GotifyNotification, e, "Failed to send Gotify notification");
}
}
protected override async Task Test(NotificationOptions model, GotifySettings settings)
{
var message = $"This is a test from Ombi, if you can see this then we have successfully pushed a notification!";
var notification = new NotificationMessage
{
Message = message,
};
await Send(notification, settings);
}
private async Task Run(NotificationOptions model, GotifySettings settings, NotificationType type)
{
var parsed = await LoadTemplate(NotificationAgent.Gotify, type, model);
if (parsed.Disabled)
{
Logger.LogInformation($"Template {type} is disabled for {NotificationAgent.Gotify}");
return;
}
var notification = new NotificationMessage
{
Message = parsed.Message,
};
await Send(notification, settings);
}
}
}

@ -0,0 +1,6 @@
namespace Ombi.Notifications.Agents
{
public interface IGotifyNotification : INotification
{
}
}

@ -4,6 +4,7 @@ using EnsureThat;
using MailKit.Net.Smtp;
using Microsoft.Extensions.Logging;
using MimeKit;
using MimeKit.Utils;
using Ombi.Core.Settings;
using Ombi.Helpers;
using Ombi.Notifications.Models;
@ -37,6 +38,15 @@ namespace Ombi.Notifications
var customization = await CustomizationSettings.GetSettingsAsync();
var html = email.LoadTemplate(model.Subject, model.Message, null, customization.Logo);
var messageId = MimeUtils.GenerateMessageId();
if (customization.ApplicationUrl.HasValue())
{
if (Uri.TryCreate(customization.ApplicationUrl, UriKind.RelativeOrAbsolute, out var url))
{
messageId = MimeUtils.GenerateMessageId(url.IdnHost);
}
}
var textBody = string.Empty;
@ -50,7 +60,8 @@ namespace Ombi.Notifications
var message = new MimeMessage
{
Body = body.ToMessageBody(),
Subject = model.Subject
Subject = model.Subject,
MessageId = messageId
};
message.From.Add(new MailboxAddress(string.IsNullOrEmpty(settings.SenderName) ? settings.SenderAddress : settings.SenderName, settings.SenderAddress));
message.To.Add(new MailboxAddress(model.To, model.To));

@ -15,6 +15,7 @@
<ItemGroup>
<ProjectReference Include="..\Ombi.Api.Discord\Ombi.Api.Discord.csproj" />
<ProjectReference Include="..\Ombi.Api.Gotify\Ombi.Api.Gotify.csproj" />
<ProjectReference Include="..\Ombi.Api.Mattermost\Ombi.Api.Mattermost.csproj" />
<ProjectReference Include="..\Ombi.Api.Notifications\Ombi.Api.Notifications.csproj" />
<ProjectReference Include="..\Ombi.Api.Pushbullet\Ombi.Api.Pushbullet.csproj" />

@ -0,0 +1,10 @@
namespace Ombi.Settings.Settings.Models.Notifications
{
public class GotifySettings : Settings
{
public bool Enabled { get; set; }
public string BaseUrl { get; set; }
public string ApplicationToken { get; set; }
public sbyte Priority { get; set; } = 4;
}
}

@ -17,6 +17,7 @@ namespace Ombi.Store.Context
{
if (_created) return;
_created = true;
Database.SetCommandTimeout(60);
Database.Migrate();

@ -5,7 +5,7 @@ using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Nito.AsyncEx;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities;
@ -20,7 +20,6 @@ namespace Ombi.Store.Repository
}
public DbSet<T> _db { get; }
private readonly U _ctx;
private readonly AsyncLock _mutex = new AsyncLock();
public async Task<T> Find(object key)
{
@ -32,7 +31,7 @@ namespace Ombi.Store.Repository
return _db.AsQueryable();
}
public async Task<T> FirstOrDefaultAsync(Expression<Func<T,bool>> predicate)
public async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate)
{
return await _db.FirstOrDefaultAsync(predicate);
}
@ -82,14 +81,11 @@ namespace Ombi.Store.Repository
await _ctx.Database.ExecuteSqlCommandAsync(sql);
}
private async Task<int> InternalSaveChanges()
protected async Task<int> InternalSaveChanges()
{
using (await _mutex.LockAsync())
{
return await _ctx.SaveChangesAsync();
}
return await _ctx.SaveChangesAsync();
}
private bool _disposed;
// Protected implementation of Dispose pattern.
@ -102,7 +98,7 @@ namespace Ombi.Store.Repository
{
_ctx?.Dispose();
}
_disposed = true;
}

@ -72,7 +72,7 @@ namespace Ombi.Store.Repository
public async Task Update(EmbyContent existingContent)
{
Db.EmbyContent.Update(existingContent);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public IQueryable<EmbyEpisode> GetAllEpisodes()
@ -83,7 +83,7 @@ namespace Ombi.Store.Repository
public async Task<EmbyEpisode> Add(EmbyEpisode content)
{
await Db.EmbyEpisode.AddAsync(content);
await Db.SaveChangesAsync();
await InternalSaveChanges();
return content;
}
public async Task<EmbyEpisode> GetEpisodeByEmbyId(string key)
@ -94,12 +94,13 @@ namespace Ombi.Store.Repository
public async Task AddRange(IEnumerable<EmbyEpisode> content)
{
Db.EmbyEpisode.AddRange(content);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public void UpdateWithoutSave(EmbyContent existingContent)
{
Db.EmbyContent.Update(existingContent);
}
}
}

@ -45,7 +45,7 @@ namespace Ombi.Store.Repository
Db.Attach(template);
Db.Entry(template).State = EntityState.Modified;
}
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task UpdateRange(IEnumerable<NotificationTemplates> templates)
@ -56,16 +56,21 @@ namespace Ombi.Store.Repository
Db.Attach(t);
Db.Entry(t).State = EntityState.Modified;
}
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task<NotificationTemplates> Insert(NotificationTemplates entity)
{
var settings = await Db.NotificationTemplates.AddAsync(entity).ConfigureAwait(false);
await Db.SaveChangesAsync().ConfigureAwait(false);
await InternalSaveChanges().ConfigureAwait(false);
return settings.Entity;
}
private async Task<int> InternalSaveChanges()
{
return await Db.SaveChangesAsync();
}
private bool _disposed;
// Protected implementation of Dispose pattern.
protected virtual void Dispose(bool disposing)

@ -96,7 +96,7 @@ namespace Ombi.Store.Repository
public async Task Update(PlexServerContent existingContent)
{
Db.PlexServerContent.Update(existingContent);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public void UpdateWithoutSave(PlexServerContent existingContent)
{
@ -106,7 +106,7 @@ namespace Ombi.Store.Repository
public async Task UpdateRange(IEnumerable<PlexServerContent> existingContent)
{
Db.PlexServerContent.UpdateRange(existingContent);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public IQueryable<PlexEpisode> GetAllEpisodes()
@ -127,14 +127,14 @@ namespace Ombi.Store.Repository
public async Task<PlexEpisode> Add(PlexEpisode content)
{
await Db.PlexEpisode.AddAsync(content);
await Db.SaveChangesAsync();
await InternalSaveChanges();
return content;
}
public async Task DeleteEpisode(PlexEpisode content)
{
Db.PlexEpisode.Remove(content);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task<PlexEpisode> GetEpisodeByKey(int key)
@ -144,7 +144,7 @@ namespace Ombi.Store.Repository
public async Task AddRange(IEnumerable<PlexEpisode> content)
{
Db.PlexEpisode.AddRange(content);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
}
}

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities.Requests;
@ -70,12 +71,12 @@ namespace Ombi.Store.Repository.Requests
Db.MovieRequests.Attach(request);
Db.Update(request);
}
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task Save()
{
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
}
}

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities.Requests;
@ -61,12 +62,12 @@ namespace Ombi.Store.Repository.Requests
Db.AlbumRequests.Attach(request);
Db.Update(request);
}
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task Save()
{
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
}
}

@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Helpers;
using Ombi.Store.Context;
using Ombi.Store.Entities.Requests;
@ -101,20 +102,20 @@ namespace Ombi.Store.Repository.Requests
public async Task Save()
{
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task<TvRequests> Add(TvRequests request)
{
await Db.TvRequests.AddAsync(request);
await Db.SaveChangesAsync();
await InternalSaveChanges();
return request;
}
public async Task<ChildRequests> AddChild(ChildRequests request)
{
await Db.ChildRequests.AddAsync(request);
await Db.SaveChangesAsync();
await InternalSaveChanges();
return request;
}
@ -122,33 +123,38 @@ namespace Ombi.Store.Repository.Requests
public async Task Delete(TvRequests request)
{
Db.TvRequests.Remove(request);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task DeleteChild(ChildRequests request)
{
Db.ChildRequests.Remove(request);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task DeleteChildRange(IEnumerable<ChildRequests> request)
{
Db.ChildRequests.RemoveRange(request);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task Update(TvRequests request)
{
Db.Update(request);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task UpdateChild(ChildRequests request)
{
Db.Update(request);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
private async Task<int> InternalSaveChanges()
{
return await Db.SaveChangesAsync();
}
}
}

@ -62,14 +62,14 @@ namespace Ombi.Store.Repository
{
//_cache.Remove(GetName(entity.SettingsName));
Db.Settings.Remove(entity);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public async Task UpdateAsync(GlobalSettings entity)
{
//_cache.Remove(GetName(entity.SettingsName));
Db.Update(entity);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public void Delete(GlobalSettings entity)
@ -91,6 +91,11 @@ namespace Ombi.Store.Repository
return $"{entity}Json";
}
private async Task<int> InternalSaveChanges()
{
return await Db.SaveChangesAsync();
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{

@ -4,6 +4,7 @@ using Ombi.Store.Entities;
using System;
using System.Linq;
using System.Threading.Tasks;
using Ombi.Helpers;
namespace Ombi.Store.Repository
{
@ -19,12 +20,16 @@ namespace Ombi.Store.Repository
public async Task CreateToken(Tokens token)
{
await Db.Tokens.AddAsync(token);
await Db.SaveChangesAsync();
await InternalSaveChanges();
}
public IQueryable<Tokens> GetToken(string tokenId)
{
return Db.Tokens.Where(x => x.Token == tokenId);
}
private async Task<int> InternalSaveChanges()
{
return await Db.SaveChangesAsync();
}
}
}

@ -98,6 +98,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Lidarr", "Ombi.Api
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Helpers.Tests", "Ombi.Helpers.Tests\Ombi.Helpers.Tests.csproj", "{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ombi.Api.Gotify", "Ombi.Api.Gotify\Ombi.Api.Gotify.csproj", "{105EA346-766E-45B8-928B-DE6991DCB7EB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ombi.Settings.Tests", "Ombi.Settings.Tests\Ombi.Settings.Tests.csproj", "{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}"
EndProject
Global
@ -258,6 +260,10 @@ Global
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3}.Release|Any CPU.Build.0 = Release|Any CPU
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{105EA346-766E-45B8-928B-DE6991DCB7EB}.Release|Any CPU.Build.0 = Release|Any CPU
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -299,6 +305,7 @@ Global
{10D1FE9D-9124-42B7-B1E1-CEB99B832618} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{4FA21A20-92F4-462C-B929-2C517A88CC56} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{CC8CEFCD-0CB6-45BB-845F-508BCAB5BDC3} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
{105EA346-766E-45B8-928B-DE6991DCB7EB} = {9293CA11-360A-4C20-A674-B9E794431BF5}
{F3969B69-3B07-4884-A7AB-0BAB8B84DF94} = {6F42AB98-9196-44C4-B888-D5E409F415A1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

@ -9,5 +9,6 @@
"typescript.tsdk": "node_modules\\typescript\\lib",
"cSpell.words": [
"usermanagement"
]
],
"discord.enabled": true
}

@ -0,0 +1,67 @@

<settings-menu></settings-menu>
<div *ngIf="form">
<fieldset>
<legend>Gotify Notifications</legend>
<div class="col-md-6">
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)">
<div class="form-group">
<div class="checkbox">
<input type="checkbox" id="enable" formControlName="enabled">
<label for="enable">Enabled</label>
</div>
</div>
<div class="form-group">
<label for="baseUrl" class="control-label">Base URL</label>
<input type="text" class="form-control form-control-custom " id="baseUrl" name="baseUrl" [ngClass]="{'form-error': form.get('baseUrl').hasError('required')}" formControlName="baseUrl" pTooltip="Enter the URL of your gotify server.">
<small *ngIf="form.get('baseUrl').hasError('required')" class="error-text">The Base URL is required</small>
</div>
<div class="form-group">
<label for="applicationToken" class="control-label">Application Token</label>
<input type="text" class="form-control form-control-custom " id="applicationToken" name="applicationToken" [ngClass]="{'form-error': form.get('applicationToken').hasError('required')}" formControlName="applicationToken" pTooltip="Enter your Application token from Gotify.">
<small *ngIf="form.get('applicationToken').hasError('required')" class="error-text">The Application Token is required</small>
</div>
<div class="form-group">
<label for="priority" class="control-label">Priority</label>
<div>
<select class="form-control form-control-custom " id="priority" name="priority" formControlName="priority" pTooltip="The priority you want your gotify notifications sent as.">
<option value="4">Normal</option>
<option value="8">High</option>
<option value="2">Low</option>
<option value="0">Lowest</option>
</select>
</div>
</div>
<div class="form-group">
<div>
<button [disabled]="form.invalid" type="button" (click)="test(form)" class="btn btn-primary-outline">
Test
<div id="spinner"></div>
</button>
</div>
</div>
<div class="form-group">
<div>
<button [disabled]="form.invalid" type="submit" id="save" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</form>
</div>
<div class="col-md-6">
<notification-templates [templates]="templates" [showSubject]="false"></notification-templates>
</div>
</fieldset>
</div>

@ -0,0 +1,68 @@
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { IGotifyNotificationSettings, INotificationTemplates, NotificationType } from "../../interfaces";
import { TesterService } from "../../services";
import { NotificationService } from "../../services";
import { SettingsService } from "../../services";
@Component({
templateUrl: "./gotify.component.html",
})
export class GotifyComponent implements OnInit {
public NotificationType = NotificationType;
public templates: INotificationTemplates[];
public form: FormGroup;
constructor(private settingsService: SettingsService,
private notificationService: NotificationService,
private fb: FormBuilder,
private testerService: TesterService) { }
public ngOnInit() {
this.settingsService.getGotifyNotificationSettings().subscribe(x => {
this.templates = x.notificationTemplates;
this.form = this.fb.group({
enabled: [x.enabled],
baseUrl: [x.baseUrl, [Validators.required]],
applicationToken: [x.applicationToken, [Validators.required]],
priority: [x.priority],
});
});
}
public onSubmit(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
return;
}
const settings = <IGotifyNotificationSettings> form.value;
settings.notificationTemplates = this.templates;
this.settingsService.saveGotifyNotificationSettings(settings).subscribe(x => {
if (x) {
this.notificationService.success("Successfully saved the Gotify settings");
} else {
this.notificationService.success("There was an error when saving the Gotify settings");
}
});
}
public test(form: FormGroup) {
if (form.invalid) {
this.notificationService.error("Please check your entered values");
return;
}
this.testerService.gotifyTest(form.value).subscribe(x => {
if (x) {
this.notificationService.success("Successfully sent a Gotify message");
} else {
this.notificationService.error("There was an error when sending the Gotify message. Please check your settings");
}
});
}
}

@ -93,6 +93,14 @@ export interface IPushoverNotificationSettings extends INotificationSettings {
sound: string;
}
export interface IGotifyNotificationSettings extends INotificationSettings {
accessToken: string;
notificationTemplates: INotificationTemplates[];
baseUrl: string;
applicationToken: string;
priority: number;
}
export interface IMattermostNotifcationSettings extends INotificationSettings {
webhookUrl: string;
username: string;

@ -80,7 +80,7 @@
<i class="fa fa-check"></i> {{ 'Common.Available' | translate }}</button>
</div>
<div *ngIf="!result.fullyAvailable">
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
<div *ngIf="result.requested || result.approved || result.monitored; then requestedBtn else notRequestedBtn"></div>
<ng-template #requestedBtn>
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]>
<i class="fa fa-check"></i> {{ 'Common.Requested' | translate }}</button>

@ -11,6 +11,7 @@ import {
IDiscordNotifcationSettings,
IEmailNotificationSettings,
IEmbyServer,
IGotifyNotificationSettings,
ILidarrSettings,
IMattermostNotifcationSettings,
IMobileNotificationTestSettings,
@ -40,7 +41,11 @@ export class TesterService extends ServiceHelpers {
}
public pushoverTest(settings: IPushoverNotificationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}pushover`, JSON.stringify(settings), {headers: this.headers});
return this.http.post<boolean>(`${this.url}pushover`, JSON.stringify(settings), { headers: this.headers });
}
public gotifyTest(settings: IGotifyNotificationSettings): Observable<boolean> {
return this.http.post<boolean>(`${this.url}gotify`, JSON.stringify(settings), { headers: this.headers });
}
public mattermostTest(settings: IMattermostNotifcationSettings): Observable<boolean> {

@ -14,6 +14,7 @@ import {
IDogNzbSettings,
IEmailNotificationSettings,
IEmbySettings,
IGotifyNotificationSettings,
IIssueSettings,
IJobSettings,
IJobSettingsViewModel,
@ -182,6 +183,14 @@ export class SettingsService extends ServiceHelpers {
.post<boolean>(`${this.url}/notifications/pushover`, JSON.stringify(settings), {headers: this.headers});
}
public getGotifyNotificationSettings(): Observable<IGotifyNotificationSettings> {
return this.http.get<IGotifyNotificationSettings>(`${this.url}/notifications/gotify`, { headers: this.headers });
}
public saveGotifyNotificationSettings(settings: IGotifyNotificationSettings): Observable<boolean> {
return this.http
.post<boolean>(`${this.url}/notifications/gotify`, JSON.stringify(settings), { headers: this.headers });
}
public getSlackNotificationSettings(): Observable<ISlackNotificationSettings> {
return this.http.get<ISlackNotificationSettings>(`${this.url}/notifications/slack`, {headers: this.headers});
}

@ -27,6 +27,7 @@ import { LidarrComponent } from "./lidarr/lidarr.component";
import { MassEmailComponent } from "./massemail/massemail.component";
import { DiscordComponent } from "./notifications/discord.component";
import { EmailNotificationComponent } from "./notifications/emailnotification.component";
import { GotifyComponent } from "./notifications/gotify.component";
import { MattermostComponent } from "./notifications/mattermost.component";
import { MobileComponent } from "./notifications/mobile.component";
import { NewsletterComponent } from "./notifications/newsletter.component";
@ -65,6 +66,7 @@ const routes: Routes = [
{ path: "Slack", component: SlackComponent, canActivate: [AuthGuard] },
{ path: "Pushover", component: PushoverComponent, canActivate: [AuthGuard] },
{ path: "Pushbullet", component: PushbulletComponent, canActivate: [AuthGuard] },
{ path: "Gotify", component: GotifyComponent, canActivate: [AuthGuard] },
{ path: "Mattermost", component: MattermostComponent, canActivate: [AuthGuard] },
{ path: "UserManagement", component: UserManagementComponent, canActivate: [AuthGuard] },
{ path: "Update", component: UpdateComponent, canActivate: [AuthGuard] },
@ -121,6 +123,7 @@ const routes: Routes = [
PushoverComponent,
MattermostComponent,
PushbulletComponent,
GotifyComponent,
UserManagementComponent,
UpdateComponent,
AboutComponent,

@ -47,6 +47,7 @@
<button mat-menu-item [routerLink]="['/Settings/Pushover']">Pushover</button>
<button mat-menu-item [routerLink]="['/Settings/Mattermost']">Mattermost</button>
<button mat-menu-item [routerLink]="['/Settings/Telegram']">Telegram</button>
<button mat-menu-item [routerLink]="['/Settings/Gotify']">Gotify</button>
</mat-menu>
<button mat-button [matMenuTriggerFor]="systemMenu"><i class="fa fa-tachometer" aria-hidden="true"></i> System</button>

@ -40,7 +40,7 @@ namespace Ombi.Controllers.V1.External
IPushbulletNotification pushbullet, ISlackNotification slack, IPushoverNotification po, IMattermostNotification mm,
IPlexApi plex, IEmbyApi emby, IRadarrApi radarr, ISonarrApi sonarr, ILogger<TesterController> log, IEmailProvider provider,
ICouchPotatoApi cpApi, ITelegramNotification telegram, ISickRageApi srApi, INewsletterJob newsletter, IMobileNotification mobileNotification,
ILidarrApi lidarrApi)
ILidarrApi lidarrApi, IGotifyNotification gotifyNotification)
{
Service = service;
DiscordNotification = notification;
@ -61,6 +61,7 @@ namespace Ombi.Controllers.V1.External
Newsletter = newsletter;
MobileNotification = mobileNotification;
LidarrApi = lidarrApi;
GotifyNotification = gotifyNotification;
}
private INotificationService Service { get; }
@ -69,6 +70,7 @@ namespace Ombi.Controllers.V1.External
private IPushbulletNotification PushbulletNotification { get; }
private ISlackNotification SlackNotification { get; }
private IPushoverNotification PushoverNotification { get; }
private IGotifyNotification GotifyNotification { get; }
private IMattermostNotification MattermostNotification { get; }
private IPlexApi PlexApi { get; }
private IRadarrApi RadarrApi { get; }
@ -155,6 +157,30 @@ namespace Ombi.Controllers.V1.External
}
/// <summary>
/// Sends a test message to Gotify using the provided settings
/// </summary>
/// <param name="settings">The settings.</param>
/// <returns></returns>
[HttpPost("gotify")]
public bool Gotify([FromBody] GotifySettings settings)
{
try
{
settings.Enabled = true;
GotifyNotification.NotifyAsync(
new NotificationOptions { NotificationType = NotificationType.Test, RequestId = -1 }, settings);
return true;
}
catch (Exception e)
{
Log.LogError(LoggingEvents.Api, e, "Could not test Gotify");
return false;
}
}
/// <summary>
/// Sends a test message to mattermost using the provided settings
/// </summary>

@ -968,6 +968,40 @@ namespace Ombi.Controllers.V1
return model;
}
/// <summary>
/// Saves the gotify notification settings.
/// </summary>
/// <param name="model">The model.</param>
/// <returns></returns>
[HttpPost("notifications/gotify")]
public async Task<bool> GotifyNotificationSettings([FromBody] GotifyNotificationViewModel model)
{
// Save the email settings
var settings = Mapper.Map<GotifySettings>(model);
var result = await Save(settings);
// Save the templates
await TemplateRepository.UpdateRange(model.NotificationTemplates);
return result;
}
/// <summary>
/// Gets the gotify Notification Settings.
/// </summary>
/// <returns></returns>
[HttpGet("notifications/gotify")]
public async Task<GotifyNotificationViewModel> GotifyNotificationSettings()
{
var settings = await Get<GotifySettings>();
var model = Mapper.Map<GotifyNotificationViewModel>(settings);
// Lookup to see if we have any templates saved
model.NotificationTemplates = BuildTemplates(NotificationAgent.Gotify);
return model;
}
/// <summary>
/// Saves the Newsletter notification settings.
/// </summary>

@ -49,6 +49,7 @@ namespace Ombi
demoInstance.Demo = demo;
instance.StoragePath = storagePath ?? string.Empty;
// Check if we need to migrate the settings
DeleteSchedules();
CheckAndMigrate();
var ctx = new SettingsContext();
var config = ctx.ApplicationConfigurations.ToList();
@ -97,6 +98,20 @@ namespace Ombi
CreateWebHostBuilder(args).Build().Run();
}
private static void DeleteSchedules()
{
try
{
if (File.Exists("Schedules.db"))
{
File.Delete("Schedules.db");
}
}
catch (Exception)
{
}
}
/// <summary>
/// This is to remove the Settings from the Ombi.db to the "new"
/// OmbiSettings.db
@ -115,7 +130,7 @@ namespace Ombi
try
{
if (ombi.Settings.Any())
if (ombi.Settings.Any() && !settings.Settings.Any())
{
// OK migrate it!
var allSettings = ombi.Settings.ToList();
@ -125,7 +140,7 @@ namespace Ombi
// Check for any application settings
if (ombi.ApplicationConfigurations.Any())
if (ombi.ApplicationConfigurations.Any() && !settings.ApplicationConfigurations.Any())
{
// OK migrate it!
var allSettings = ombi.ApplicationConfigurations.ToList();

@ -61,9 +61,6 @@ namespace Ombi
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<OmbiContext>();
services.AddIdentity<OmbiUser, IdentityRole>()
.AddEntityFrameworkStores<OmbiContext>()
.AddDefaultTokenProviders()

@ -1766,9 +1766,9 @@
}
},
"bootstrap": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.0.tgz",
"integrity": "sha512-F1yTDO9OHKH0xjl03DsOe8Nu1OWBIeAugGMhy3UTIYDdbbIPesQIhCEbj+HEr6wqlwweGAlP8F3OBC6kEuhFuw=="
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.4.1.tgz",
"integrity": "sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA=="
},
"bootswatch": {
"version": "3.4.0",
@ -4190,8 +4190,7 @@
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"optional": true
"bundled": true
},
"aproba": {
"version": "1.2.0",
@ -4209,13 +4208,11 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"optional": true
"bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -4228,18 +4225,15 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"core-util-is": {
"version": "1.0.2",
@ -4342,8 +4336,7 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"optional": true
"bundled": true
},
"ini": {
"version": "1.3.5",
@ -4353,7 +4346,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -4366,20 +4358,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"optional": true
"bundled": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -4396,7 +4385,6 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -4469,8 +4457,7 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"object-assign": {
"version": "4.1.1",
@ -4480,7 +4467,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -4556,8 +4542,7 @@
},
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"optional": true
"bundled": true
},
"safer-buffer": {
"version": "2.1.2",
@ -4587,7 +4572,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -4605,7 +4589,6 @@
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -4644,13 +4627,11 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"optional": true
"bundled": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"optional": true
"bundled": true
}
}
},

Loading…
Cancel
Save