Add Greenfield Store Email API

This commit is contained in:
Kukks
2022-03-11 10:17:50 +01:00
committed by Andrew Camilleri
parent 326eb1135b
commit dca986eb2e
10 changed files with 380 additions and 93 deletions

View File

@@ -7,6 +7,24 @@ namespace BTCPayServer.Client
{ {
public partial class BTCPayServerClient public partial class BTCPayServerClient
{ {
public virtual async Task<EmailSettingsData> GetStoreEmailSettings(string storeId,
CancellationToken token = default)
{
using var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/email", method: HttpMethod.Get),
token);
return await HandleResponse<EmailSettingsData>(response);
}
public virtual async Task<EmailSettingsData> UpdateStoreEmailSettings(string storeId, EmailSettingsData request,
CancellationToken token = default)
{
using var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/email", bodyPayload: request, method: HttpMethod.Put),
token);
return await HandleResponse<EmailSettingsData>(response);
}
public virtual async Task SendEmail(string storeId, SendEmailRequest request, public virtual async Task SendEmail(string storeId, SendEmailRequest request,
CancellationToken token = default) CancellationToken token = default)
{ {

View File

@@ -23,7 +23,7 @@ namespace BTCPayServer.Client
await HandleResponse(response); await HandleResponse(response);
} }
public virtual async Task<StoreData> AddStoreUser(string storeId, StoreUserData request, public virtual async Task AddStoreUser(string storeId, StoreUserData request,
CancellationToken token = default) CancellationToken token = default)
{ {
if (request == null) if (request == null)
@@ -31,7 +31,7 @@ namespace BTCPayServer.Client
using var response = await _httpClient.SendAsync( using var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/users", bodyPayload: request, method: HttpMethod.Post), CreateHttpRequest($"api/v1/stores/{storeId}/users", bodyPayload: request, method: HttpMethod.Post),
token); token);
return await HandleResponse<StoreData>(response); await HandleResponse(response);
} }
} }
} }

View File

@@ -0,0 +1,33 @@
namespace BTCPayServer.Client.Models;
public class EmailSettingsData
{
public string Server
{
get; set;
}
public int? Port
{
get; set;
}
public string Login
{
get; set;
}
public string Password
{
get; set;
}
public string FromDisplay
{
get; set;
}
public string From
{
get; set;
}
}

View File

@@ -2292,13 +2292,27 @@ namespace BTCPayServer.Tests
var admin = tester.NewAccount(); var admin = tester.NewAccount();
await admin.GrantAccessAsync(true); await admin.GrantAccessAsync(true);
var adminClient = await admin.CreateClient(Policies.Unrestricted); var adminClient = await admin.CreateClient(Policies.Unrestricted);
await adminClient.UpdateStoreEmailSettings(admin.StoreId,
new EmailSettingsData());
await adminClient.SendEmail(admin.StoreId, new SendEmailRequest() var data = new EmailSettingsData()
{ {
Body = "lol", From = "admin@admin.com",
Subject = "subj", Login = "admin@admin.com",
Email = "sdasdas" Password = "admin@admin.com",
}); Port = 1234,
Server = "admin.com",
};
await adminClient.UpdateStoreEmailSettings(admin.StoreId, data);
var s = await adminClient.GetStoreEmailSettings(admin.StoreId);
Assert.Equal(JsonConvert.SerializeObject(s), JsonConvert.SerializeObject(data));
await AssertValidationError(new[] { nameof(EmailSettingsData.From) },
async () => await adminClient.UpdateStoreEmailSettings(admin.StoreId,
new EmailSettingsData() { From = "ass" }));
await adminClient.SendEmail(admin.StoreId,
new SendEmailRequest() { Body = "lol", Subject = "subj", Email = "sdasdas" });
} }
} }
} }

View File

@@ -5,7 +5,10 @@ using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Services.Mails; using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Stores;
using BTCPayServer.Validation;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -18,10 +21,12 @@ namespace BTCPayServer.Controllers.GreenField
public class GreenfieldStoreEmailController : Controller public class GreenfieldStoreEmailController : Controller
{ {
private readonly EmailSenderFactory _emailSenderFactory; private readonly EmailSenderFactory _emailSenderFactory;
private readonly StoreRepository _storeRepository;
public GreenfieldStoreEmailController(EmailSenderFactory emailSenderFactory) public GreenfieldStoreEmailController(EmailSenderFactory emailSenderFactory, StoreRepository storeRepository)
{ {
_emailSenderFactory = emailSenderFactory; _emailSenderFactory = emailSenderFactory;
_storeRepository = storeRepository;
} }
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@@ -43,5 +48,48 @@ namespace BTCPayServer.Controllers.GreenField
emailSender.SendEmail(request.Email, request.Subject, request.Body); emailSender.SendEmail(request.Email, request.Subject, request.Body);
return Ok(); return Ok();
} }
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/email")]
public IActionResult GetStoreEmailSettings()
{
var store = HttpContext.GetStoreData();
return store == null ? StoreNotFound() : Ok(FromModel(store));
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPut("~/api/v1/stores/{storeId}/email")]
public async Task<IActionResult> UpdateStoreEmailSettings(string storeId, EmailSettings request)
{
var store = HttpContext.GetStoreData();
if (store == null)
{
return StoreNotFound();
}
if (!string.IsNullOrEmpty(request.From) && !EmailValidator.IsEmail(request.From))
{
request.AddModelError(e => e.From,
"Invalid email address", this);
return this.CreateValidationError(ModelState);
}
var blob = store.GetStoreBlob();
blob.EmailSettings = request;
if (store.SetStoreBlob(blob))
{
await _storeRepository.UpdateStore(store);
}
return Ok(FromModel(store));
}
private EmailSettings FromModel(Data.StoreData data)
{
return data.GetStoreBlob().EmailSettings??new();
}
private IActionResult StoreNotFound()
{
return this.CreateAPIError(404, "store-not-found", "The store was not found");
}
} }
} }

View File

@@ -12,6 +12,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers.GreenField; using BTCPayServer.Controllers.GreenField;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Security.Greenfield; using BTCPayServer.Security.Greenfield;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -22,6 +23,8 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using NBitcoin; using NBitcoin;
using NBXplorer.Models; using NBXplorer.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using InvoiceData = BTCPayServer.Client.Models.InvoiceData; using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
using Language = BTCPayServer.Client.Models.Language; using Language = BTCPayServer.Client.Models.Language;
using NotificationData = BTCPayServer.Client.Models.NotificationData; using NotificationData = BTCPayServer.Client.Models.NotificationData;
@@ -40,7 +43,10 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly IOptionsMonitor<IdentityOptions> _identityOptions; private readonly IOptionsMonitor<IdentityOptions> _identityOptions;
private readonly GreenfieldStoreOnChainPaymentMethodsController _chainPaymentMethodsController; private readonly GreenfieldStoreOnChainPaymentMethodsController _chainPaymentMethodsController;
private readonly GreenfieldStoreOnChainWalletsController _storeOnChainWalletsController; private readonly GreenfieldStoreOnChainWalletsController _storeOnChainWalletsController;
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController
_storeLightningNetworkPaymentMethodsController;
private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController; private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
private readonly GreenfieldHealthController _healthController; private readonly GreenfieldHealthController _healthController;
private readonly GreenfieldPaymentRequestsController _paymentRequestController; private readonly GreenfieldPaymentRequestsController _paymentRequestController;
@@ -58,6 +64,7 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly UIHomeController _homeController; private readonly UIHomeController _homeController;
private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController; private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController;
private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController; private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController;
private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
public BTCPayServerClientFactory(StoreRepository storeRepository, public BTCPayServerClientFactory(StoreRepository storeRepository,
@@ -82,6 +89,7 @@ namespace BTCPayServer.Controllers.Greenfield
UIHomeController homeController, UIHomeController homeController,
GreenfieldStorePaymentMethodsController storePaymentMethodsController, GreenfieldStorePaymentMethodsController storePaymentMethodsController,
GreenfieldStoreEmailController greenfieldStoreEmailController, GreenfieldStoreEmailController greenfieldStoreEmailController,
GreenfieldStoreUsersController greenfieldStoreUsersController,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
@@ -106,6 +114,7 @@ namespace BTCPayServer.Controllers.Greenfield
_homeController = homeController; _homeController = homeController;
_storePaymentMethodsController = storePaymentMethodsController; _storePaymentMethodsController = storePaymentMethodsController;
_greenfieldStoreEmailController = greenfieldStoreEmailController; _greenfieldStoreEmailController = greenfieldStoreEmailController;
_greenfieldStoreUsersController = greenfieldStoreUsersController;
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
@@ -124,12 +133,14 @@ namespace BTCPayServer.Controllers.Greenfield
claims.AddRange((await _userManager.GetRolesAsync(user)).Select(s => claims.AddRange((await _userManager.GetRolesAsync(user)).Select(s =>
new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s))); new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s)));
context.User = context.User =
new ClaimsPrincipal(new ClaimsIdentity(claims, $"Local{GreenfieldConstants.AuthenticationType}WithUser")); new ClaimsPrincipal(new ClaimsIdentity(claims,
$"Local{GreenfieldConstants.AuthenticationType}WithUser"));
} }
else else
{ {
context.User = context.User =
new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>(), $"Local{GreenfieldConstants.AuthenticationType}")); new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>(),
$"Local{GreenfieldConstants.AuthenticationType}"));
} }
if (storeIds?.Any() is true) if (storeIds?.Any() is true)
@@ -163,12 +174,13 @@ namespace BTCPayServer.Controllers.Greenfield
_homeController, _homeController,
_storePaymentMethodsController, _storePaymentMethodsController,
_greenfieldStoreEmailController, _greenfieldStoreEmailController,
_greenfieldStoreUsersController,
new LocalHttpContextAccessor() { HttpContext = context } new LocalHttpContextAccessor() { HttpContext = context }
); );
} }
} }
public class LocalHttpContextAccessor: IHttpContextAccessor public class LocalHttpContextAccessor : IHttpContextAccessor
{ {
public HttpContext? HttpContext { get; set; } public HttpContext? HttpContext { get; set; }
} }
@@ -185,7 +197,10 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly GreenfieldStoresController _storesController; private readonly GreenfieldStoresController _storesController;
private readonly GreenfieldStoreLightningNodeApiController _storeLightningNodeApiController; private readonly GreenfieldStoreLightningNodeApiController _storeLightningNodeApiController;
private readonly GreenfieldInternalLightningNodeApiController _lightningNodeApiController; private readonly GreenfieldInternalLightningNodeApiController _lightningNodeApiController;
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController _storeLightningNetworkPaymentMethodsController;
private readonly GreenfieldStoreLightningNetworkPaymentMethodsController
_storeLightningNetworkPaymentMethodsController;
private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController; private readonly GreenfieldStoreLNURLPayPaymentMethodsController _storeLnurlPayPaymentMethodsController;
private readonly GreenfieldInvoiceController _greenFieldInvoiceController; private readonly GreenfieldInvoiceController _greenFieldInvoiceController;
private readonly GreenfieldServerInfoController _greenFieldServerInfoController; private readonly GreenfieldServerInfoController _greenFieldServerInfoController;
@@ -194,6 +209,7 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly UIHomeController _homeController; private readonly UIHomeController _homeController;
private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController; private readonly GreenfieldStorePaymentMethodsController _storePaymentMethodsController;
private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController; private readonly GreenfieldStoreEmailController _greenfieldStoreEmailController;
private readonly GreenfieldStoreUsersController _greenfieldStoreUsersController;
public LocalBTCPayServerClient( public LocalBTCPayServerClient(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
@@ -216,6 +232,7 @@ namespace BTCPayServer.Controllers.Greenfield
UIHomeController homeController, UIHomeController homeController,
GreenfieldStorePaymentMethodsController storePaymentMethodsController, GreenfieldStorePaymentMethodsController storePaymentMethodsController,
GreenfieldStoreEmailController greenfieldStoreEmailController, GreenfieldStoreEmailController greenfieldStoreEmailController,
GreenfieldStoreUsersController greenfieldStoreUsersController,
IHttpContextAccessor httpContextAccessor) : base(new Uri("https://dummy.local"), "", "") IHttpContextAccessor httpContextAccessor) : base(new Uri("https://dummy.local"), "", "")
{ {
_chainPaymentMethodsController = chainPaymentMethodsController; _chainPaymentMethodsController = chainPaymentMethodsController;
@@ -237,6 +254,7 @@ namespace BTCPayServer.Controllers.Greenfield
_homeController = homeController; _homeController = homeController;
_storePaymentMethodsController = storePaymentMethodsController; _storePaymentMethodsController = storePaymentMethodsController;
_greenfieldStoreEmailController = greenfieldStoreEmailController; _greenfieldStoreEmailController = greenfieldStoreEmailController;
_greenfieldStoreUsersController = greenfieldStoreUsersController;
var controllers = new[] var controllers = new[]
{ {
@@ -244,8 +262,8 @@ namespace BTCPayServer.Controllers.Greenfield
paymentRequestController, apiKeysController, notificationsController, usersController, paymentRequestController, apiKeysController, notificationsController, usersController,
storeLightningNetworkPaymentMethodsController, greenFieldInvoiceController, storeWebhooksController, storeLightningNetworkPaymentMethodsController, greenFieldInvoiceController, storeWebhooksController,
greenFieldServerInfoController, greenfieldPullPaymentController, storesController, homeController, greenFieldServerInfoController, greenfieldPullPaymentController, storesController, homeController,
lightningNodeApiController, storeLightningNodeApiController as ControllerBase, storePaymentMethodsController, lightningNodeApiController, storeLightningNodeApiController as ControllerBase,
greenfieldStoreEmailController storePaymentMethodsController, greenfieldStoreEmailController, greenfieldStoreUsersController
}; };
var authoverride = new DefaultAuthorizationService( var authoverride = new DefaultAuthorizationService(
@@ -259,8 +277,6 @@ namespace BTCPayServer.Controllers.Greenfield
serviceProvider.GetRequiredService<IAuthorizationHandlerContextFactory>(), serviceProvider.GetRequiredService<IAuthorizationHandlerContextFactory>(),
serviceProvider.GetRequiredService<IAuthorizationEvaluator>(), serviceProvider.GetRequiredService<IAuthorizationEvaluator>(),
serviceProvider.GetRequiredService<IOptions<AuthorizationOptions>>() serviceProvider.GetRequiredService<IOptions<AuthorizationOptions>>()
); );
@@ -268,7 +284,8 @@ namespace BTCPayServer.Controllers.Greenfield
{ {
controller.ControllerContext.HttpContext = httpContextAccessor.HttpContext; controller.ControllerContext.HttpContext = httpContextAccessor.HttpContext;
var authInterface = typeof(IAuthorizationService); var authInterface = typeof(IAuthorizationService);
foreach (FieldInfo fieldInfo in controller.GetType().GetFields().Where(info => authInterface.IsAssignableFrom(info.FieldType))) foreach (FieldInfo fieldInfo in controller.GetType().GetFields()
.Where(info => authInterface.IsAssignableFrom(info.FieldType)))
{ {
fieldInfo.SetValue(controller, authoverride); fieldInfo.SetValue(controller, authoverride);
} }
@@ -283,12 +300,14 @@ namespace BTCPayServer.Controllers.Greenfield
private readonly UserManager<ApplicationUser> _userManager; private readonly UserManager<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
public AuthHandlerProvider(StoreRepository storeRepository, UserManager<ApplicationUser> userManager, IHttpContextAccessor httpContextAccessor) public AuthHandlerProvider(StoreRepository storeRepository, UserManager<ApplicationUser> userManager,
IHttpContextAccessor httpContextAccessor)
{ {
_storeRepository = storeRepository; _storeRepository = storeRepository;
_userManager = userManager; _userManager = userManager;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
} }
public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context) public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)
{ {
return Task.FromResult<IEnumerable<IAuthorizationHandler>>(new IAuthorizationHandler[] return Task.FromResult<IEnumerable<IAuthorizationHandler>>(new IAuthorizationHandler[]
@@ -297,6 +316,7 @@ namespace BTCPayServer.Controllers.Greenfield
}); });
} }
} }
protected override HttpRequestMessage CreateHttpRequest(string path, protected override HttpRequestMessage CreateHttpRequest(string path,
Dictionary<string, object> queryPayload = null, HttpMethod method = null) Dictionary<string, object> queryPayload = null, HttpMethod method = null)
{ {
@@ -511,7 +531,8 @@ namespace BTCPayServer.Controllers.Greenfield
await _lightningNodeApiController.GetDepositAddress(cryptoCode)); await _lightningNodeApiController.GetDepositAddress(cryptoCode));
} }
public override async Task<LightningPaymentData> PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request, public override async Task<LightningPaymentData> PayLightningInvoice(string cryptoCode,
PayLightningInvoiceRequest request,
CancellationToken token = default) CancellationToken token = default)
{ {
return GetFromActionResult<LightningPaymentData>( return GetFromActionResult<LightningPaymentData>(
@@ -575,7 +596,8 @@ namespace BTCPayServer.Controllers.Greenfield
public override Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId, public override Task<IEnumerable<OnChainPaymentMethodData>> GetStoreOnChainPaymentMethods(string storeId,
bool? enabled, CancellationToken token) bool? enabled, CancellationToken token)
{ {
return Task.FromResult(GetFromActionResult(_chainPaymentMethodsController.GetOnChainPaymentMethods(storeId, enabled))); return Task.FromResult(
GetFromActionResult(_chainPaymentMethodsController.GetOnChainPaymentMethods(storeId, enabled)));
} }
public override Task<OnChainPaymentMethodData> GetStoreOnChainPaymentMethod(string storeId, public override Task<OnChainPaymentMethodData> GetStoreOnChainPaymentMethod(string storeId,
@@ -596,7 +618,8 @@ namespace BTCPayServer.Controllers.Greenfield
CancellationToken token = default) CancellationToken token = default)
{ {
return GetFromActionResult<OnChainPaymentMethodData>( return GetFromActionResult<OnChainPaymentMethodData>(
await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode, new UpdateOnChainPaymentMethodRequest( await _chainPaymentMethodsController.UpdateOnChainPaymentMethod(storeId, cryptoCode,
new UpdateOnChainPaymentMethodRequest(
enabled: paymentMethod.Enabled, enabled: paymentMethod.Enabled,
label: paymentMethod.Label, label: paymentMethod.Label,
accountKeyPath: paymentMethod.AccountKeyPath, accountKeyPath: paymentMethod.AccountKeyPath,
@@ -606,7 +629,8 @@ namespace BTCPayServer.Controllers.Greenfield
public override Task<OnChainPaymentMethodPreviewResultData> PreviewProposedStoreOnChainPaymentMethodAddresses( public override Task<OnChainPaymentMethodPreviewResultData> PreviewProposedStoreOnChainPaymentMethodAddresses(
string storeId, string cryptoCode, string storeId, string cryptoCode,
UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10, CancellationToken token = default) UpdateOnChainPaymentMethodRequest paymentMethod, int offset = 0, int amount = 10,
CancellationToken token = default)
{ {
return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>( return Task.FromResult(GetFromActionResult<OnChainPaymentMethodPreviewResultData>(
_chainPaymentMethodsController.GetProposedOnChainPaymentMethodPreview(storeId, cryptoCode, _chainPaymentMethodsController.GetProposedOnChainPaymentMethodPreview(storeId, cryptoCode,
@@ -636,7 +660,8 @@ namespace BTCPayServer.Controllers.Greenfield
public override async Task<PaymentRequestData> GetPaymentRequest(string storeId, string paymentRequestId, public override async Task<PaymentRequestData> GetPaymentRequest(string storeId, string paymentRequestId,
CancellationToken token = default) CancellationToken token = default)
{ {
return GetFromActionResult<PaymentRequestData>(await _paymentRequestController.GetPaymentRequest(storeId, paymentRequestId)); return GetFromActionResult<PaymentRequestData>(
await _paymentRequestController.GetPaymentRequest(storeId, paymentRequestId));
} }
public override async Task ArchivePaymentRequest(string storeId, string paymentRequestId, public override async Task ArchivePaymentRequest(string storeId, string paymentRequestId,
@@ -882,7 +907,8 @@ namespace BTCPayServer.Controllers.Greenfield
{ {
return GetFromActionResult<LightningNetworkPaymentMethodData>(await return GetFromActionResult<LightningNetworkPaymentMethodData>(await
_storeLightningNetworkPaymentMethodsController.UpdateLightningNetworkPaymentMethod(storeId, cryptoCode, _storeLightningNetworkPaymentMethodsController.UpdateLightningNetworkPaymentMethod(storeId, cryptoCode,
new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString, paymentMethod.Enabled))); new UpdateLightningNetworkPaymentMethodRequest(paymentMethod.ConnectionString,
paymentMethod.Enabled)));
} }
public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null, public override async Task<IEnumerable<InvoiceData>> GetInvoices(string storeId, string[] orderId = null,
@@ -894,7 +920,6 @@ namespace BTCPayServer.Controllers.Greenfield
int? skip = null, int? skip = null,
int? take = null, int? take = null,
CancellationToken token = default CancellationToken token = default
) )
{ {
return GetFromActionResult<IEnumerable<InvoiceData>>( return GetFromActionResult<IEnumerable<InvoiceData>>(
@@ -980,7 +1005,8 @@ namespace BTCPayServer.Controllers.Greenfield
public override Task<Language[]> GetAvailableLanguages(CancellationToken token = default) public override Task<Language[]> GetAvailableLanguages(CancellationToken token = default)
{ {
return Task.FromResult(_homeController.LanguageService.GetLanguages().Select(language => new Language(language.Code, language.DisplayName)).ToArray()); return Task.FromResult(_homeController.LanguageService.GetLanguages()
.Select(language => new Language(language.Code, language.DisplayName)).ToArray());
} }
public override Task<PermissionMetadata[]> GetPermissionMetadata(CancellationToken token = default) public override Task<PermissionMetadata[]> GetPermissionMetadata(CancellationToken token = default)
@@ -988,15 +1014,19 @@ namespace BTCPayServer.Controllers.Greenfield
return Task.FromResult(GetFromActionResult<PermissionMetadata[]>(_homeController.Permissions())); return Task.FromResult(GetFromActionResult<PermissionMetadata[]>(_homeController.Permissions()));
} }
public override async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId, bool? enabled = null, CancellationToken token = default) public override async Task<Dictionary<string, GenericPaymentMethodData>> GetStorePaymentMethods(string storeId,
bool? enabled = null, CancellationToken token = default)
{ {
return GetFromActionResult(await _storePaymentMethodsController.GetStorePaymentMethods(storeId, enabled)); return GetFromActionResult(await _storePaymentMethodsController.GetStorePaymentMethods(storeId, enabled));
} }
public override async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId, string cryptoCode, GenerateOnChainWalletRequest request, public override async Task<OnChainPaymentMethodDataWithSensitiveData> GenerateOnChainWallet(string storeId,
string cryptoCode, GenerateOnChainWalletRequest request,
CancellationToken token = default) CancellationToken token = default)
{ {
return GetFromActionResult<OnChainPaymentMethodDataWithSensitiveData>(await _chainPaymentMethodsController.GenerateOnChainWallet(storeId, cryptoCode, new GenerateWalletRequest() return GetFromActionResult<OnChainPaymentMethodDataWithSensitiveData>(
await _chainPaymentMethodsController.GenerateOnChainWallet(storeId, cryptoCode,
new GenerateWalletRequest()
{ {
Passphrase = request.Passphrase, Passphrase = request.Passphrase,
AccountNumber = request.AccountNumber, AccountNumber = request.AccountNumber,
@@ -1009,9 +1039,53 @@ namespace BTCPayServer.Controllers.Greenfield
})); }));
} }
public override async Task SendEmail(string storeId, SendEmailRequest request, CancellationToken token = default) public override async Task SendEmail(string storeId, SendEmailRequest request,
CancellationToken token = default)
{ {
HandleActionResult(await _greenfieldStoreEmailController.SendEmailFromStore(storeId, request)); HandleActionResult(await _greenfieldStoreEmailController.SendEmailFromStore(storeId, request));
} }
public override Task<EmailSettingsData> GetStoreEmailSettings(string storeId, CancellationToken token = default)
{
return Task.FromResult(
GetFromActionResult<EmailSettingsData>(_greenfieldStoreEmailController.GetStoreEmailSettings()));
}
public override async Task<EmailSettingsData> UpdateStoreEmailSettings(string storeId,
EmailSettingsData request, CancellationToken token = default)
{
return GetFromActionResult<EmailSettingsData>(
await _greenfieldStoreEmailController.UpdateStoreEmailSettings(storeId,
JObject.FromObject(request).ToObject<EmailSettings>()));
}
public override async Task<ApplicationUserData[]> GetUsers(CancellationToken token = default)
{
return GetFromActionResult(await _usersController.GetUsers());
}
public override Task<IEnumerable<StoreUserData>> GetStoreUsers(string storeId,
CancellationToken token = default)
{
return Task.FromResult(
GetFromActionResult<IEnumerable<StoreUserData>>(_greenfieldStoreUsersController.GetStoreUsers()));
}
public override async Task AddStoreUser(string storeId, StoreUserData request,
CancellationToken token = default)
{
HandleActionResult(await _greenfieldStoreUsersController.AddStoreUser(storeId, request));
}
public override async Task RemoveStoreUser(string storeId, string userId, CancellationToken token = default)
{
HandleActionResult(await _greenfieldStoreUsersController.RemoveStoreUser(storeId, userId));
}
public override async Task<ApplicationUserData> GetUserByIdOrEmail(string idOrEmail,
CancellationToken token = default)
{
return GetFromActionResult<ApplicationUserData>(await _usersController.GetUser(idOrEmail));
}
} }
} }

View File

@@ -4,48 +4,15 @@ using System.Net.Security;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Client.Models;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
using MimeKit; using MimeKit;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace BTCPayServer.Services.Mails namespace BTCPayServer.Services.Mails
{ {
public class EmailSettings public class EmailSettings :EmailSettingsData
{ {
[Display(Name = "SMTP Server")]
public string Server
{
get; set;
}
public int? Port
{
get; set;
}
public string Login
{
get; set;
}
public string Password
{
get; set;
}
[Display(Name = "Sender's display name")]
public string FromDisplay
{
get; set;
}
[EmailAddress]
[Display(Name = "Sender's email address")]
public string From
{
get; set;
}
public bool IsComplete() public bool IsComplete()
{ {
return !string.IsNullOrWhiteSpace(Server) && return !string.IsNullOrWhiteSpace(Server) &&

View File

@@ -28,7 +28,7 @@
<div asp-validation-summary="All" class="text-danger"></div> <div asp-validation-summary="All" class="text-danger"></div>
} }
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.Server" class="form-label"></label> <label asp-for="Settings.Server" class="form-label">SMTP Server</label>
<input asp-for="Settings.Server" data-fill="server" class="form-control"/> <input asp-for="Settings.Server" data-fill="server" class="form-control"/>
<span asp-validation-for="Settings.Server" class="text-danger"></span> <span asp-validation-for="Settings.Server" class="text-danger"></span>
</div> </div>
@@ -38,7 +38,7 @@
<span asp-validation-for="Settings.Port" class="text-danger"></span> <span asp-validation-for="Settings.Port" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.FromDisplay" class="form-label"></label> <label asp-for="Settings.FromDisplay" class="form-label">Sender's display name</label>
<input asp-for="Settings.FromDisplay" class="form-control"/> <input asp-for="Settings.FromDisplay" class="form-control"/>
<small class="form-text text-muted"> <small class="form-text text-muted">
Some email providers (like Gmail) don't allow you to set your display name, so this setting may not have any effect. Some email providers (like Gmail) don't allow you to set your display name, so this setting may not have any effect.
@@ -46,7 +46,7 @@
<span asp-validation-for="Settings.FromDisplay" class="text-danger"></span> <span asp-validation-for="Settings.FromDisplay" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="Settings.From" class="form-label"></label> <label asp-for="Settings.From" class="form-label">Sender's email address</label>
<input asp-for="Settings.From" class="form-control"/> <input asp-for="Settings.From" class="form-control"/>
<span asp-validation-for="Settings.From" class="text-danger"></span> <span asp-validation-for="Settings.From" class="text-danger"></span>
</div> </div>

View File

@@ -1,9 +1,109 @@
{ {
"paths": { "paths": {
"/api/v1/stores/{storeId}/email": {
"get": {
"tags": [
"Stores (Email)"
],
"summary": "Get store email settings",
"parameters": [
{
"name": "storeId",
"in": "path",
"required": true,
"description": "The store to fetch",
"schema": {
"type": "string"
}
}
],
"description": "View email settings of the specified store",
"operationId": "Stores_GetStoreEmailSettings",
"responses": {
"200": {
"description": "specified store email settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EmailSettingsData"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden to view the specified store"
},
"404": {
"description": "The key is not found for this store"
}
},
"security": [
{
"API_Key": [
"btcpay.store.canmodifystoresettings"
],
"Basic": []
}
]
},
"post": {
"tags": [
"Stores (Email)"
],
"summary": "Update store email settings",
"description": "Update a store's email settings",
"operationId": "Stores_UpdateStoreEmailSettings",
"requestBody": {
"x-name": "request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EmailSettingsData"
}
}
},
"required": true,
"x-position": 1
},
"responses": {
"200": {
"description": "The settings were updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/EmailSettingsData"
}
}
}
},
"400": {
"description": "A list of errors that occurred when updating the settings",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ValidationProblemDetails"
}
}
}
},
"403": {
"description": "If you are authenticated but forbidden to modify the store"
}
},
"security": [
{
"API_Key": [
"btcpay.store.canmodifystoresettings"
],
"Basic": []
}
]
}
},
"/api/v1/stores/{storeId}/email/send": { "/api/v1/stores/{storeId}/email/send": {
"post": { "post": {
"tags": [ "tags": [
"Stores" "Stores (Email)"
], ],
"summary": "Send an email for a store", "summary": "Send an email for a store",
"description": "Send an email using the store's SMTP server", "description": "Send an email using the store's SMTP server",
@@ -63,12 +163,43 @@
"description": "Body of the email to send as plain text." "description": "Body of the email to send as plain text."
} }
} }
},
"EmailSettingsData": {
"type": "object",
"additionalProperties": false,
"properties": {
"server": {
"type": "string",
"description": "Smtp server host"
},
"port": {
"type": "number",
"description": "Smtp server port"
},
"login": {
"type": "string",
"description": "Smtp server username"
},
"password": {
"type": "string",
"description": "Smtp server password"
},
"from": {
"type": "string",
"format": "email",
"description": "Email to send from"
},
"fromDisplay": {
"type": "string",
"description": "The name of the sender"
}
}
} }
} }
}, },
"tags": [ "tags": [
{ {
"name": "Store Email" "name": "Stores (Email)"
} }
] ]
} }

View File

@@ -52,6 +52,7 @@
], ],
"summary": "Add a store user", "summary": "Add a store user",
"description": "Add a store user", "description": "Add a store user",
"operationId": "Stores_AddStoreUser",
"requestBody": { "requestBody": {
"x-name": "request", "x-name": "request",
"content": { "content": {
@@ -66,7 +67,7 @@
}, },
"responses": { "responses": {
"200": { "200": {
"description": "The user as added" "description": "The user was added"
}, },
"400": { "400": {
"description": "A list of errors that occurred when creating the store", "description": "A list of errors that occurred when creating the store",
@@ -108,6 +109,7 @@
"Stores (Users)" "Stores (Users)"
], ],
"summary": "Remove Store User", "summary": "Remove Store User",
"operationId": "Stores_RemoveStoreUser",
"description": "Removes the specified store user. If there is no other owner, this endpoint will fail.", "description": "Removes the specified store user. If there is no other owner, this endpoint will fail.",
"parameters": [ "parameters": [
{ {