diff --git a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs index 68fc29199..a182fa98f 100644 --- a/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs +++ b/BTCPayServer.Abstractions/Extensions/ViewsRazor.cs @@ -97,6 +97,7 @@ namespace BTCPayServer.Abstractions.Extensions return categoryAndPageMatch && idMatch; } + [Obsolete()] public static bool IsPageActive(this ViewDataDictionary viewData, IEnumerable pages, object id = null) where T : IConvertible { diff --git a/BTCPayServer.Data/Data/EmailRuleData.cs b/BTCPayServer.Data/Data/EmailRuleData.cs index b0f37f006..cf836672d 100644 --- a/BTCPayServer.Data/Data/EmailRuleData.cs +++ b/BTCPayServer.Data/Data/EmailRuleData.cs @@ -69,16 +69,29 @@ public static partial class ApplicationDbContextExtensions public static IQueryable GetRules(this IQueryable query, string storeId) => query.Where(o => o.StoreId == storeId) .OrderBy(o => o.Id); + public static IQueryable GetServerRules(this IQueryable query) + => query.Where(o => o.StoreId == null).OrderBy(o => o.Id); public static Task GetMatches(this DbSet set, string? storeId, string trigger, JObject model) - => set - .FromSqlInterpolated($""" - SELECT * FROM email_rules - WHERE store_id IS NOT DISTINCT FROM {storeId} AND trigger = {trigger} AND (condition IS NULL OR jsonb_path_exists({model.ToString()}::JSONB, condition::JSONPATH)) - """) - .ToArrayAsync(); + => + storeId is null + ? set + .FromSqlInterpolated($""" + SELECT * FROM email_rules + WHERE store_id IS NULL AND trigger = {trigger} AND (condition IS NULL OR jsonb_path_exists({model.ToString()}::JSONB, condition::JSONPATH)) + """) + .ToArrayAsync() + : set + .FromSqlInterpolated($""" + SELECT * FROM email_rules + WHERE store_id = {storeId} AND trigger = {trigger} AND (condition IS NULL OR jsonb_path_exists({model.ToString()}::JSONB, condition::JSONPATH)) + """) + .ToArrayAsync(); public static Task GetRule(this IQueryable query, string storeId, long id) => query.Where(o => o.StoreId == storeId && o.Id == id) .FirstOrDefaultAsync(); + public static Task GetServerRule(this IQueryable query, long id) + => query.Where(o => o.StoreId == null && o.Id == id) + .FirstOrDefaultAsync(); } diff --git a/BTCPayServer.Tests/PMO/ConfigureEmailPMO.cs b/BTCPayServer.Tests/PMO/ConfigureEmailPMO.cs index 27ffa811a..b0746f1c1 100644 --- a/BTCPayServer.Tests/PMO/ConfigureEmailPMO.cs +++ b/BTCPayServer.Tests/PMO/ConfigureEmailPMO.cs @@ -1,5 +1,6 @@ #nullable enable using System.Threading.Tasks; +using BTCPayServer.Abstractions.Models; namespace BTCPayServer.Tests.PMO; @@ -14,6 +15,17 @@ public class ConfigureEmailPMO(PlaywrightTester s) public string? Password { get; set; } public bool? EnabledCertificateCheck { get; set; } } + public async Task UseMailPit() + { + await s.Page.ClickAsync("#mailpit"); + await s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info); + } + + public async Task ConfigureEmailRules() + { + await s.Page.ClickAsync("#ConfigureEmailRules"); + return new EmailRulesPMO(s); + } public Task FillMailPit(Form form) => Fill(new() diff --git a/BTCPayServer.Tests/PMO/EmailRulePMO.cs b/BTCPayServer.Tests/PMO/EmailRulePMO.cs index 4aabe8ad9..30cac29a5 100644 --- a/BTCPayServer.Tests/PMO/EmailRulePMO.cs +++ b/BTCPayServer.Tests/PMO/EmailRulePMO.cs @@ -3,6 +3,15 @@ using System.Threading.Tasks; namespace BTCPayServer.Tests.PMO; +public class EmailRulesPMO(PlaywrightTester s) +{ + public async Task CreateEmailRule() + { + await s.Page.ClickAsync("#CreateEmailRule"); + return new EmailRulePMO(s); + } +} + public class EmailRulePMO(PlaywrightTester s) { public class Form @@ -13,6 +22,7 @@ public class EmailRulePMO(PlaywrightTester s) public string? Body { get; set; } public bool? CustomerEmail { get; set; } public string? Condition { get; set; } + public bool HtmlBody { get; set; } } public async Task Fill(Form form) @@ -26,7 +36,18 @@ public class EmailRulePMO(PlaywrightTester s) if (form.Subject is not null) await s.Page.FillAsync("#Subject", form.Subject); if (form.Body is not null) - await s.Page.Locator(".note-editable").FillAsync(form.Body); + { + if (form.HtmlBody) + { + await s.Page.ClickAsync(".btn-codeview"); + await s.Page.FillAsync(".note-codable", form.Body); + } + else + { + await s.Page.FillAsync(".note-editable", form.Body); + } + } + if (form.CustomerEmail is {} v) await s.Page.SetCheckedAsync("#AdditionalData_CustomerEmail", v); await s.ClickPagePrimary(); diff --git a/BTCPayServer.Tests/PlaywrightTester.cs b/BTCPayServer.Tests/PlaywrightTester.cs index d4add1fcb..97cc0eb69 100644 --- a/BTCPayServer.Tests/PlaywrightTester.cs +++ b/BTCPayServer.Tests/PlaywrightTester.cs @@ -25,7 +25,7 @@ namespace BTCPayServer.Tests public class PlaywrightTester : IAsyncDisposable { public Uri ServerUri; - private string CreatedUser; + public string CreatedUser; internal string InvoiceId; public Logging.ILog TestLogs => Server.TestLogs; public IPage Page { get; set; } @@ -369,7 +369,11 @@ namespace BTCPayServer.Tests public async Task GoToServer(ServerNavPages navPages = ServerNavPages.Policies) { await Page.ClickAsync("#menu-item-Policies"); - if (navPages != ServerNavPages.Policies) + if (navPages == ServerNavPages.Emails) + { + await Page.ClickAsync($"#menu-item-Server-{navPages}"); + } + else if (navPages != ServerNavPages.Policies) { await Page.ClickAsync($"#menu-item-{navPages}"); } diff --git a/BTCPayServer.Tests/PlaywrightTests.cs b/BTCPayServer.Tests/PlaywrightTests.cs index 228445917..66efb4120 100644 --- a/BTCPayServer.Tests/PlaywrightTests.cs +++ b/BTCPayServer.Tests/PlaywrightTests.cs @@ -1496,8 +1496,7 @@ namespace BTCPayServer.Tests await s.AddDerivationScheme(); await s.GoToInvoices(); - var sent = await s.Server.WaitForEvent(() => s.CreateInvoice(amount: 10m, currency: "USD")); - var message = await s.Server.AssertHasEmail(sent); + var message = await s.Server.AssertHasEmail(() => s.CreateInvoice(amount: 10m, currency: "USD")); Assert.Equal("Invoice has been created in USD for 10!", message.Text); await s.GoToUrl(rulesUrl); @@ -1529,11 +1528,27 @@ namespace BTCPayServer.Tests }); await s.GoToInvoices(); - sent = await s.Server.WaitForEvent(() => s.CreateInvoice(amount: 10m, currency: "USD", refundEmail: "john@test.com")); - message = await s.Server.AssertHasEmail(sent); + message = await s.Server.AssertHasEmail(() => s.CreateInvoice(amount: 10m, currency: "USD", refundEmail: "john@test.com")); Assert.Equal("Invoice Created in USD for " + storeName + "!", message.Subject); Assert.Equal("Invoice has been created in USD for 10!", message.Text); Assert.Equal("john@test.com", message.To[0].Address); + + await s.GoToServer(ServerNavPages.Emails); + + await mailPMO.UseMailPit(); + var rules = await mailPMO.ConfigureEmailRules(); + await rules.CreateEmailRule(); + await pmo.Fill(new() + { + Trigger = "SRV-PasswordReset", + HtmlBody = true, + Body = "

Hello, click here to reset the password

" + }); + await s.Logout(); + await s.Page.GetByRole(AriaRole.Link, new() { Name = "Forgot password?" }).ClickAsync(); + await s.Page.FillAsync("#Email", s.CreatedUser); + message = await s.Server.AssertHasEmail(() => s.ClickPagePrimary()); + Assert.Contains("

Hello, AssertHasEmail(Func action) + { + var sent = await WaitForEvent(action); + return await AssertHasEmail(sent); + } + public MailPitClient GetMailPitClient() { var http = PayTester.GetService().CreateClient("MAIL_PIT"); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 68076042a..0e89febef 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -3352,7 +3352,7 @@ namespace BTCPayServer.Tests Assert.Equal("admin@admin.com", (await Assert.IsType(await emailSenderFactory.GetEmailSender()).GetEmailSettings()).Login); Assert.Null(await Assert.IsType(await emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings()); - Assert.IsType(await acc.GetController().StoreEmailSettings(acc.StoreId, new EmailsViewModel(new EmailSettings + Assert.IsType(await acc.GetController().ServerEmailSettings(new (new EmailSettings { From = "store@store.com", Login = "store@store.com", @@ -3363,13 +3363,11 @@ namespace BTCPayServer.Tests Assert.Equal("store@store.com", (await Assert.IsType(await emailSenderFactory.GetEmailSender(acc.StoreId)).GetEmailSettings()).Login); - var sent = await tester.WaitForEvent( - async () => - { - var sender = await emailSenderFactory.GetEmailSender(acc.StoreId); - sender.SendEmail(MailboxAddress.Parse("destination@test.com"), "test", "hello world"); - }); - var message = await tester.AssertHasEmail(sent); + var message = await tester.AssertHasEmail(async () => + { + var sender = await emailSenderFactory.GetEmailSender(acc.StoreId); + sender.SendEmail(MailboxAddress.Parse("destination@test.com"), "test", "hello world"); + }); Assert.Equal("test", message.Subject); Assert.Equal("hello world", message.Text); } diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 2c1f1b0f3..e504a43da 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -51,6 +51,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -193,5 +197,12 @@ + + <_ContentIncludedByDefault Remove="Plugins\Emails\Views\UIStoreEmailRules\StoreEmailRulesList.cshtml" /> + <_ContentIncludedByDefault Remove="Plugins\Emails\Views\UIStoreEmailRules\StoreEmailRulesManage.cshtml" /> + <_ContentIncludedByDefault Remove="Plugins\Emails\Views\UIServerEmail\ServerEmailSettings.cshtml" /> + <_ContentIncludedByDefault Remove="Plugins\Emails\Views\UIStoresEmail\StoreEmailSettings.cshtml" /> + + diff --git a/BTCPayServer/Components/MainNav/Default.cshtml b/BTCPayServer/Components/MainNav/Default.cshtml index afae34451..23c874541 100644 --- a/BTCPayServer/Components/MainNav/Default.cshtml +++ b/BTCPayServer/Components/MainNav/Default.cshtml @@ -314,7 +314,7 @@ Roles