Compare commits

..

1 Commits

Author SHA1 Message Date
Kukks
4aebaa0e33 attempt to use a compiled to .net version of ccxt 2019-02-28 16:39:11 +01:00
670 changed files with 4044 additions and 81568 deletions

View File

@ -1,132 +0,0 @@
version: 2
jobs:
build:
machine:
docker_layer_caching: false
steps:
- checkout
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
publish_docker_linuxamd64:
machine:
docker_layer_caching: false
steps:
- checkout
- run:
command: |
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
if [ -z "$LATEST_TAG" ]
then
LATEST_TAG="latest"
fi
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f Dockerfiles/amd64.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
publish_docker_linuxarm32:
machine:
docker_layer_caching: false
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
if [ -z "$LATEST_TAG" ]
then
LATEST_TAG="latest"
fi
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f Dockerfiles/arm32v7.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
publish_docker_linuxarm64:
machine:
docker_layer_caching: false
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
if [ -z "$LATEST_TAG" ]
then
LATEST_TAG="latest"
fi
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f Dockerfiles/arm64v8.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
publish_docker_multiarch:
machine:
enabled: true
image: circleci/classic:201808-01
steps:
- run:
command: |
# Turn on Experimental features
sudo mkdir $HOME/.docker
sudo sh -c 'echo "{ \"experimental\": \"enabled\" }" >> $HOME/.docker/config.json'
#
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
#
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
if [ -z "$LATEST_TAG" ]
then
LATEST_TAG="latest"
fi
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 --os linux --arch amd64
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 --os linux --arch arm64 --variant v8
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
trigger_docs_build:
machine:
enabled: true
image: circleci/classic:201808-01
steps:
- run:
command: |
curl -X POST -H "Authorization: token $GH_PAT" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/btcpayserver/btcpayserver-doc/dispatches --data '{"event_type": "build_docs"}'
workflows:
version: 2
publish:
jobs:
- trigger_docs_build:
filters:
branches:
only: master
# only act on version tags
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_linuxamd64:
filters:
# ignore any commit on any branch by default
branches:
only: master
# only act on version tags
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_linuxarm32:
filters:
branches:
only: master
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_linuxarm64:
filters:
branches:
only: master
tags:
only: /v[0-9]+(\.[0-9]+)*/
- publish_docker_multiarch:
requires:
- publish_docker_linuxamd64
- publish_docker_linuxarm32
- publish_docker_linuxarm64
filters:
branches:
only: master
tags:
only: /v[0-9]+(\.[0-9]+)*/

5
.gitignore vendored
View File

@ -299,8 +299,3 @@ BtcTransmuter/mydb.db
BtcTransmuter\.Extension\.BtcPayServer/obj/Debug/netcoreapp2\.2/
BtcTransmuter\.Extension\.BtcPayServer/obj/
BtcTransmuter/Extensions/
/BtcTransmuter/Extensions/
/BtcTransmuter/mydb.db_old
dest

View File

@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
</ItemGroup>
</Project>

View File

@ -1,16 +0,0 @@
using Microsoft.Extensions.Configuration;
namespace BTCTransmuter.Extension.Tor.Configuration
{
public class BTCTransmuterTorOptions
{
public string TorrcFile { get; }
public string TransmuterHiddenServiceName { get; }
public BTCTransmuterTorOptions(IConfiguration configuration)
{
TorrcFile = configuration.GetValue<string>(nameof(TorrcFile), null);
TransmuterHiddenServiceName = configuration.GetValue(nameof(TransmuterHiddenServiceName), "BTCTransmuter");
}
}
}

View File

@ -1,44 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BTCTransmuter.Extension.Tor.Configuration;
using BTCTransmuter.Extension.Tor.Services;
using Microsoft.Extensions.Hosting;
namespace BTCTransmuter.Extension.Tor.HostedServices
{
public class TorServicesHostedService : IHostedService
{
private readonly BTCTransmuterTorOptions _btcTransmuterTorOptions;
private readonly TorServices _torServices;
public TorServicesHostedService(BTCTransmuterTorOptions btcTransmuterTorOptions, TorServices torServices)
{
_btcTransmuterTorOptions = btcTransmuterTorOptions;
_torServices = torServices;
}
public Task StartAsync(CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(_btcTransmuterTorOptions.TorrcFile))
{
_ = ContinuouslyMonitorTorServices(cancellationToken);
}
return Task.CompletedTask;
}
private async Task ContinuouslyMonitorTorServices(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
_torServices.Refresh();
await Task.Delay(TimeSpan.FromMinutes(2), cancellationToken);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}

View File

@ -1,9 +0,0 @@
namespace BTCTransmuter.Extension.Tor.Models
{
public class TorService
{
public string Name { get; set; }
public string OnionHost { get; set; }
public int VirtualPort { get; set; }
}
}

View File

@ -1,91 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using BTCTransmuter.Extension.Tor.Configuration;
using BTCTransmuter.Extension.Tor.Models;
using Microsoft.Extensions.Logging;
namespace BTCTransmuter.Extension.Tor.Services
{
public class TorServices
{
private readonly BTCTransmuterTorOptions _transmuterTorOptions;
private readonly ILogger<TorServices> _logger;
public TorServices(BTCTransmuterTorOptions transmuterTorOptions, ILogger<TorServices> logger)
{
_transmuterTorOptions = transmuterTorOptions;
_logger = logger;
}
public TorService[] Services { get; internal set; } = Array.Empty<TorService>();
public TorService TransmuterTorService
{
get
{
if (string.IsNullOrEmpty(_transmuterTorOptions.TransmuterHiddenServiceName))
{
return null;
}
return Services.SingleOrDefault(service =>
service.Name.Equals(_transmuterTorOptions.TransmuterHiddenServiceName,
StringComparison.InvariantCultureIgnoreCase));
}
}
internal void Refresh()
{
if (string.IsNullOrEmpty(_transmuterTorOptions.TorrcFile) || !File.Exists(_transmuterTorOptions.TorrcFile))
{
if (!string.IsNullOrEmpty(_transmuterTorOptions.TorrcFile))
_logger.LogWarning("Torrc file is not found");
Services = Array.Empty<TorService>();
return;
}
List<TorService> result = new List<TorService>();
try
{
var torrcContent = File.ReadAllText(_transmuterTorOptions.TorrcFile);
if (!Torrc.TryParse(torrcContent, out var torrc))
{
_logger.LogWarning("Torrc file could not be parsed");
Services = Array.Empty<TorService>();
return;
}
var services = torrc.ServiceDirectories.SelectMany(d => d.ServicePorts.Select(p => (Directory: new DirectoryInfo(d.DirectoryPath), VirtualPort: p.VirtualPort)))
.Select(d => (ServiceName: d.Directory.Name,
ReadingLines: System.IO.File.ReadAllLines(Path.Combine(d.Directory.FullName, "hostname")),
VirtualPort: d.VirtualPort))
.ToArray();
foreach (var service in services)
{
try
{
var onionHost = (service.ReadingLines)[0].Trim();
var torService = new TorService()
{
Name = service.ServiceName,
OnionHost = $"http://{onionHost}",
VirtualPort = service.VirtualPort
};
result.Add(torService);
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Error while reading hidden service {service.ServiceName} configuration");
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Error while reading torrc file");
}
Services = result.ToArray();
}
}
}

View File

@ -1,98 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
namespace BTCTransmuter.Extension.Tor.Services
{
public class Torrc
{
public static bool TryParse(string str, out Torrc value)
{
value = null;
List<HiddenServiceDir> serviceDirectories = new List<HiddenServiceDir>();
var lines = str.Split(new char[] { '\n' });
HiddenServiceDir currentDirectory = null;
foreach (var line in lines)
{
if (HiddenServiceDir.TryParse(line, out var dir))
{
serviceDirectories.Add(dir);
currentDirectory = dir;
}
else if (HiddenServicePortDefinition.TryParse(line, out var portDef) && currentDirectory != null)
{
currentDirectory.ServicePorts.Add(portDef);
}
}
value = new Torrc() { ServiceDirectories = serviceDirectories };
return true;
}
public List<HiddenServiceDir> ServiceDirectories { get; set; } = new List<HiddenServiceDir>();
public override string ToString()
{
StringBuilder builder = new StringBuilder();
foreach(var serviceDir in ServiceDirectories)
{
builder.AppendLine(serviceDir.ToString());
foreach (var port in serviceDir.ServicePorts)
builder.AppendLine(port.ToString());
}
return builder.ToString();
}
}
public class HiddenServiceDir
{
public static bool TryParse(string str, out HiddenServiceDir serviceDir)
{
serviceDir = null;
if (!str.Trim().StartsWith("HiddenServiceDir ", StringComparison.OrdinalIgnoreCase))
return false;
var parts = str.Split(new char[] { ' ', '\t' }, StringSplitOptions.None);
if (parts.Length != 2)
return false;
serviceDir = new HiddenServiceDir() { DirectoryPath = parts[1].Trim() };
return true;
}
public string DirectoryPath { get; set; }
public List<HiddenServicePortDefinition> ServicePorts { get; set; } = new List<HiddenServicePortDefinition>();
public override string ToString()
{
return $"HiddenServiceDir {DirectoryPath}";
}
}
public class HiddenServicePortDefinition
{
public static bool TryParse(string str, out HiddenServicePortDefinition portDefinition)
{
portDefinition = null;
if (!str.Trim().StartsWith("HiddenServicePort ", StringComparison.OrdinalIgnoreCase))
return false;
var parts = str.Split(new char[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 3)
return false;
if (!int.TryParse(parts[1].Trim(), out int virtualPort))
return false;
var addressPort = parts[2].Trim().Split(new []{':'}, StringSplitOptions.RemoveEmptyEntries);
if (addressPort.Length != 2)
return false;
if (!int.TryParse(addressPort[1].Trim(), out int port))
return false;
if (!IPAddress.TryParse(addressPort[0].Trim(), out IPAddress address))
return false;
portDefinition = new HiddenServicePortDefinition() { VirtualPort = virtualPort, Endpoint = new IPEndPoint(address, port) };
return true;
}
public int VirtualPort { get; set; }
public IPEndPoint Endpoint { get; set; }
public override string ToString()
{
return $"HiddenServicePort {VirtualPort} {Endpoint}";
}
}
}

View File

@ -1,22 +0,0 @@
using System;
using BtcTransmuter.Abstractions.Extensions;
using BTCTransmuter.Extension.Tor.Configuration;
using BTCTransmuter.Extension.Tor.Services;
using Microsoft.Extensions.DependencyInjection;
namespace BTCTransmuter.Extension.Tor
{
public class TorBtcTransmuterExtension : BtcTransmuterExtension
{
public override string Name => "Tor Plugin";
public override string Description => "Allows you to access Transmuter over Tor";
public override string MenuPartial => "TorOnionLinkPartial";
public override void Execute(IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<BTCTransmuterTorOptions>();
serviceCollection.AddSingleton<TorServices>();
base.Execute(serviceCollection);
}
}
}

View File

@ -1,7 +0,0 @@
@inject BTCTransmuter.Extension.Tor.Services.TorServices TorServices
@if (TorServices.TransmuterTorService != null)
{
<li class="nav-item">
<a href="@TorServices.TransmuterTorService.OnionHost" class="nav-link">Onion</a>
</li>
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AssemblyName>BtcTransmuter.Extension.Timer</AssemblyName>
<RootNamespace>BtcTransmuter.Extension.Timer</RootNamespace>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Styles\**;Views\**;Scripts\**" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Timer\EditData.cshtml" />
</ItemGroup>
</Project>

View File

@ -6,24 +6,22 @@ using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Abstractions.Triggers;
using BtcTransmuter.Extension.Timer.Triggers.Timer;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace BtcTransmuter.Extension.Timer.HostedServices
{
public class TimerHostedService : IHostedService
{
private readonly ITriggerDispatcher _triggerDispatcher;
private readonly ILogger<TimerHostedService> _logger;
private readonly IRecipeManager _recipeManager;
public TimerHostedService(ITriggerDispatcher triggerDispatcher, ILogger<TimerHostedService> logger)
public TimerHostedService(ITriggerDispatcher triggerDispatcher, IRecipeManager recipeManager)
{
_triggerDispatcher = triggerDispatcher;
_logger = logger;
_recipeManager = recipeManager;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Starting Timer Service");
_ = Loop(cancellationToken);
return Task.CompletedTask;
}
@ -32,7 +30,7 @@ namespace BtcTransmuter.Extension.Timer.HostedServices
{
while (!cancellationToken.IsCancellationRequested)
{
_ = _triggerDispatcher.DispatchTrigger(new TimerTrigger());
await _triggerDispatcher.DispatchTrigger(new TimerTrigger());
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
}
}

View File

@ -5,6 +5,7 @@ namespace BtcTransmuter.Extension.Timer
public class TimerBtcTransmuterExtension : BtcTransmuterExtension
{
public override string Name => "Timer Plugin";
public override string Version => "0.0.1";
protected override int Priority => 0;
}
}

View File

@ -1,5 +1,4 @@
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Abstractions.Triggers;
using BtcTransmuter.Data.Entities;
@ -12,12 +11,11 @@ using Microsoft.Extensions.Caching.Memory;
namespace BtcTransmuter.Extension.Timer.Triggers.Timer
{
[Authorize]
[Route("timer-plugin/triggers/[controller]")]
[Route("timer-plugin/triggers/timer")]
public class TimerController : BaseTriggerController<TimerController.TimerTriggerViewModel, TimerTriggerParameters>
{
public TimerController(IRecipeManager recipeManager, UserManager<User> userManager, IMemoryCache memoryCache,
IExternalServiceManager externalServiceManager) :
base(recipeManager, userManager, memoryCache, externalServiceManager)
public TimerController(IRecipeManager recipeManager, UserManager<User> userManager, IMemoryCache memoryCache) :
base(recipeManager, userManager, memoryCache)
{
}

View File

@ -21,14 +21,14 @@ namespace BtcTransmuter.Extension.Timer.Triggers.Timer
"Trigger a recipe every X time";
public override string ViewPartial => "ViewTimerTrigger";
public override string ControllerName => "Timer";
protected override string ControllerName => "Timer";
protected override Task<bool> IsTriggered(ITrigger trigger, RecipeTrigger recipeTrigger,
TimerTriggerData triggerData,
TimerTriggerParameters parameters)
{
if (parameters.StartOn.HasValue && parameters.StartOn > DateTime.Now)
if (parameters.StartOn.HasValue && parameters.StartOn < DateTime.Now)
{
return Task.FromResult(false);
}

View File

@ -6,10 +6,8 @@ namespace BtcTransmuter.Extension.Timer.Triggers.Timer
public class TimerTriggerParameters
{
public DateTime? LastTriggered { get; set; }
[Display(Name = "Start from")]
public DateTime? StartOn { get; set; }
[Required][Display(Name = "Trigger every")]
[Required]
public int TriggerEveryAmount { get; set; }
[Required]
public TimerResetEvery TriggerEvery { get; set; }

View File

@ -1,4 +1,3 @@
@using System.Globalization
@using BtcTransmuter.Extension.Timer.Triggers.Timer
@model BtcTransmuter.Extension.Timer.Triggers.Timer.TimerController.TimerTriggerViewModel
@ -14,31 +13,20 @@
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="StartOn" class="control-label"></label>
<div class="input-group">
<input type="datetime-local" asp-for="StartOn"
value="@(Model.StartOn?.ToString("u", CultureInfo.InvariantCulture))"
class="form-control flatdtpicker" placeholder="Start from"/>
<div class="input-group-append">
<button class="btn btn-secondary input-group-clear" type="button" title="Clear">
&times;
</button>
</div>
</div>
<input type="datetime-local" asp-for="StartOn" class="form-control datetime"/>
<span asp-validation-for="StartOn" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="TriggerEveryAmount" class="control-label"></label>
<div class="input-group">
<input type="number" asp-for="TriggerEveryAmount" placeholder="Amount" class="form-control">
<select class="custom-select" asp-for="TriggerEvery" asp-items="@Html.GetEnumSelectList(typeof(TimerTriggerParameters.TimerResetEvery))">
<select class="custom-select" asp-for="TriggerEvery" asp-items="@Html.GetEnumSelectList(typeof(TimerTriggerParameters.TimerResetEvery))">
</select>
</div>
<span asp-validation-for="TriggerEveryAmount" class="text-danger"></span>
<span asp-validation-for="TriggerEvery" class="text-danger"></span>
</div>
<input type="hidden" asp-for="RecipeId"/>
<input type="hidden" asp-for="RecipeId" />
<div class="mt-2">
<button type="submit" class="btn btn-primary">Save</button>
<a asp-action="EditRecipe" asp-controller="Recipes" class="btn btn-secondary" asp-route-id="@Model.RecipeId">Back to Recipe</a>

View File

@ -1,30 +1,8 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BtcTransmuter.Abstractions.Actions
{
public class ActionHandlerResult
{
public bool Executed { get; set; }
public string Result { get; set; }
public object Data { get; protected set; }
public virtual string DataJson => JsonConvert.SerializeObject(Data);
}
// public class StringJsonResult
// {
// public StringJsonResult()
// {
// }
//
// public StringJsonResult(string value)
// {
// Value = value;
// }
//
// public string Value { get; set; }
// }
}

View File

@ -1,7 +1,4 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Data.Entities;
using Microsoft.AspNetCore.Authorization;
@ -11,31 +8,21 @@ using Microsoft.Extensions.Caching.Memory;
namespace BtcTransmuter.Abstractions.Actions
{
public interface IActionViewModel
{
string RecipeId { get; set; }
string RecipeActionIdInGroupBeforeThisOne { get; set; }
}
[Authorize]
public abstract class BaseActionController<TViewModel, TRecipeActionData> : Controller
where TViewModel : TRecipeActionData, IActionViewModel
where TViewModel : TRecipeActionData
{
private readonly IMemoryCache _memoryCache;
protected readonly UserManager<User> _userManager;
protected readonly IRecipeManager _recipeManager;
private readonly IExternalServiceManager _externalServiceManager;
private string RecipeActionIdInGroupBeforeThisOne;
protected BaseActionController(IMemoryCache memoryCache,
UserManager<User> userManager,
IRecipeManager recipeManager,
IExternalServiceManager externalServiceManager)
IRecipeManager recipeManager)
{
_memoryCache = memoryCache;
_userManager = userManager;
_recipeManager = recipeManager;
_externalServiceManager = externalServiceManager;
}
[HttpGet("{identifier}")]
@ -47,11 +34,7 @@ namespace BtcTransmuter.Abstractions.Actions
return result.Error;
}
var vm = await BuildViewModel(result.Data);
vm.RecipeId = result.Data.RecipeId;
vm.RecipeActionIdInGroupBeforeThisOne = RecipeActionIdInGroupBeforeThisOne;
return View(vm);
return View(await BuildViewModel(result.Data));
}
[HttpPost("{identifier}")]
@ -67,22 +50,8 @@ namespace BtcTransmuter.Abstractions.Actions
if (modelResult.showViewModel != null)
{
modelResult.showViewModel.RecipeId = result.Data.RecipeId;
modelResult.showViewModel.RecipeActionIdInGroupBeforeThisOne = RecipeActionIdInGroupBeforeThisOne;
return View(modelResult.showViewModel);
}
if (!string.IsNullOrEmpty(modelResult.ToSave.ExternalServiceId))
{
var externalService= await _externalServiceManager.GetExternalServiceData(
modelResult.ToSave.ExternalServiceId,
GetUserId());
if (externalService == null)
{
return BadRequest("what you tryin to pull bro");
}
modelResult.ToSave.ExternalService = externalService;
}
await _recipeManager.AddOrUpdateRecipeAction(modelResult.ToSave);
@ -94,7 +63,6 @@ namespace BtcTransmuter.Abstractions.Actions
}
protected abstract Task<TViewModel> BuildViewModel(RecipeAction recipeAction);
protected abstract Task<(RecipeAction ToSave, TViewModel showViewModel)> BuildModel(
TViewModel viewModel, RecipeAction mainModel);
@ -118,31 +86,6 @@ namespace BtcTransmuter.Abstractions.Actions
}), null);
}
if (!string.IsNullOrEmpty(data.RecipeActionGroupId))
{
var recipeActionGroup = recipe.RecipeActionGroups.Single(group =>
group.Id.Equals(data.RecipeActionGroupId, StringComparison.InvariantCultureIgnoreCase));
var actions = recipeActionGroup.RecipeActions.OrderBy(action => action.Order);
var matched = actions.Select((action, i) => (action, i))
.Where(tuple => tuple.Item1.ActionId == data.Id);
var index = matched.Any()
? actions.Select((action, i) => (action, i))
.Where(tuple => tuple.Item1.ActionId == data.Id)
.Select(tuple => tuple.Item2)
.FirstOrDefault()
: -1;
if (index == -1 && actions.Any())
{
RecipeActionIdInGroupBeforeThisOne = actions.Last().Id;
}
else if (index > 0)
{
RecipeActionIdInGroupBeforeThisOne = actions.ElementAt(index - 1).Id;
}
}
return (null, data);
}

View File

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -10,11 +8,10 @@ using BtcTransmuter.Data.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
namespace BtcTransmuter.Abstractions.Actions
{
public abstract class BaseActionHandler<TActionData, TActionResultData> : IActionHandler, IActionDescriptor
public abstract class BaseActionHandler<TActionData> : IActionHandler, IActionDescriptor
{
public abstract string ActionId { get; }
public abstract string Name { get; }
@ -22,8 +19,6 @@ namespace BtcTransmuter.Abstractions.Actions
public abstract string ViewPartial { get; }
public abstract string ControllerName { get; }
public Type ActionResultDataType => typeof(TActionResultData);
public virtual Task<IActionResult> EditData(RecipeAction data)
{
@ -45,29 +40,44 @@ namespace BtcTransmuter.Abstractions.Actions
}
}
public virtual Task<bool> CanExecute(Dictionary<string, (object data, string json)> data, RecipeAction recipeAction)
protected abstract Task<bool> CanExecute(object triggerData, RecipeAction recipeAction);
public async Task<ActionHandlerResult> Execute(object triggerData, RecipeAction recipeAction)
{
return Task.FromResult(recipeAction.ActionId == ActionId);
if (await CanExecute(triggerData, recipeAction))
{
return await Execute(triggerData, recipeAction, recipeAction.Get<TActionData>());
}
return new ActionHandlerResult()
{
Executed = false
};
}
public Task<ActionHandlerResult> Execute(Dictionary<string, (object data, string json)> data, RecipeAction recipeAction)
{
return Execute(data.ToDictionary(pair => pair.Key, pair => pair.Value.data), recipeAction);
}
public async Task<ActionHandlerResult> Execute(Dictionary<string, object> data, RecipeAction recipeAction)
{
return await Execute(data, recipeAction, recipeAction.Get<TActionData>());
}
protected abstract Task<TypedActionHandlerResult<TActionResultData>> Execute(Dictionary<string, object> data, RecipeAction recipeAction,
protected abstract Task<ActionHandlerResult> Execute(object triggerData, RecipeAction recipeAction,
TActionData actionData);
/// <summary>
/// https://dotnetfiddle.net/MoqJFk
/// </summary>
protected static string InterpolateString(string value, Dictionary<string, object> data)
protected static string InterpolateString(string value, object @object)
{
return InterpolationHelper.InterpolateString(value, data);
try
{
return Regex.Replace(value, @"{(.+?)}",
match =>
{
var p = Expression.Parameter(@object.GetType(), "TriggerData");
var e = System.Linq.Dynamic.DynamicExpression.ParseLambda(new[] {p}, null,
match.Groups[1].Value);
return (e.Compile().DynamicInvoke(@object) ?? "").ToString();
});
}
catch (Exception e)
{
return value;
}
}
}
}

View File

@ -1,4 +1,3 @@
using System;
using System.Threading.Tasks;
using BtcTransmuter.Data.Entities;
using Microsoft.AspNetCore.Mvc;
@ -12,7 +11,6 @@ namespace BtcTransmuter.Abstractions.Actions
string Description { get; }
string ViewPartial { get; }
Type ActionResultDataType { get; }
Task<IActionResult> EditData(RecipeAction data);
}
}

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using BtcTransmuter.Data;
using BtcTransmuter.Data.Entities;
@ -7,9 +6,6 @@ namespace BtcTransmuter.Abstractions.Actions
{
public interface IActionDispatcher
{
Task<IEnumerable<ActionHandlerResult>> Dispatch(Dictionary<string, (object data, string json)> triggerData,
RecipeAction recipeAction);
Task Dispatch(Dictionary<string, (object data, string json)> data, RecipeActionGroup recipeActionGroup);
Task Dispatch(object triggerData, RecipeAction recipeAction);
}
}

View File

@ -1,17 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BtcTransmuter.Data;
using BtcTransmuter.Data.Entities;
namespace BtcTransmuter.Abstractions.Actions
{
public interface IActionHandler
{
Type ActionResultDataType { get; }
Task<ActionHandlerResult> Execute(Dictionary<string, (object data, string json)> data,
RecipeAction recipeAction);
Task<bool> CanExecute(Dictionary<string, (object data, string json)> data, RecipeAction recipeAction);
Task<ActionHandlerResult> Execute(object triggerData, RecipeAction recipeAction);
}
}

View File

@ -1,11 +0,0 @@
namespace BtcTransmuter.Abstractions.Actions
{
public class TypedActionHandlerResult<T>: ActionHandlerResult
{
public T TypedData
{
get => (T) Data;
set => Data = value;
}
}
}

View File

@ -1,18 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.0" />
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="3.1.4" />
<PackageReference Include="NetCore.AutoRegisterDi" Version="2.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.1.1" />
<PackageReference Include="ExtCore.Mvc.Infrastructure" Version="4.0.0" />
<PackageReference Include="Microsoft.Extensions.Identity.Stores" Version="2.2.0" />
<PackageReference Include="NetCore.AutoRegisterDi" Version="1.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="wilx.System.Linq.Dynamic.Core" Version="2.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Data\BtcTransmuter.Data.csproj" />

View File

@ -1,8 +0,0 @@
namespace BtcTransmuter
{
public enum DatabaseType
{
Sqlite,
Postgres
}
}

View File

@ -1,15 +0,0 @@
using System;
namespace BtcTransmuter
{
public interface IBtcTransmuterOptions
{
string ExtensionsDir { get; set; }
string DatabaseConnectionString { get; set; }
string DataProtectionDir { get; set; }
DatabaseType DatabaseType { get; set; }
bool UseDatabaseColumnEncryption { get; set; }
bool DisableInternalAuth { get; set; }
Uri BTCPayAuthServer { get; set; }
}
}

View File

@ -1,31 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using BtcTransmuter.Abstractions.Actions;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Helpers;
using BtcTransmuter.Abstractions.Triggers;
using ExtCore.Infrastructure;
using ExtCore.Infrastructure.Actions;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NetCore.AutoRegisterDi;
using Newtonsoft.Json;
namespace BtcTransmuter.Abstractions.Extensions
{
public abstract class BtcTransmuterExtension: IExtension
public abstract class BtcTransmuterExtension : ExtensionBase, IConfigureAction, IConfigureServicesAction
{
int IConfigureServicesAction.Priority => Priority;
public abstract string Name { get; }
public virtual string Version => GetType().Assembly.GetName().Version.ToString();
public virtual string Description { get; } = string.Empty;
public virtual string Authors { get; } = string.Empty;
public virtual string HeaderPartial { get; }
public virtual string MenuPartial { get; }
public virtual IEnumerable<JsonConverter> JsonConverters { get; set; }
int IConfigureAction.Priority => Priority;
protected abstract int Priority { get; }
public string HeaderPartial { get; }
public IEnumerable<IActionDescriptor> Actions => GetInstancesOfTypeInOurAssembly<IActionDescriptor>();
public IEnumerable<ITriggerDescriptor> Triggers => GetInstancesOfTypeInOurAssembly<ITriggerDescriptor>();
@ -38,12 +34,7 @@ namespace BtcTransmuter.Abstractions.Extensions
{
}
public void Execute(IServiceCollection serviceCollection, IServiceProvider serviceProvider)
{
Execute(serviceCollection);
}
public virtual void Execute(IServiceCollection serviceCollection)
public virtual void Execute(IServiceCollection serviceCollection, IServiceProvider serviceProvider)
{
RegisterInstances(serviceCollection, new Type[]
{
@ -51,17 +42,13 @@ namespace BtcTransmuter.Abstractions.Extensions
typeof(IActionHandler),
typeof(ITriggerDescriptor),
typeof(ITriggerHandler),
typeof(IHostedService),
typeof(IExternalServiceDescriptor),
typeof(TransmuterInterpolationTypeProvider),
});
RegisterInstances(serviceCollection, new Type[]
{
typeof(IHostedService)
}, ServiceLifetime.Singleton);
serviceCollection.AddSingleton(this);
}
private void RegisterInstances(IServiceCollection serviceCollection, Type[] validTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
private void RegisterInstances(IServiceCollection serviceCollection, Type[] validTypes)
{
var types = serviceCollection
.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(GetType()))
@ -74,16 +61,14 @@ namespace BtcTransmuter.Abstractions.Extensions
foreach (var type in types.TypesToConsider)
{
Console.WriteLine($"Registering {type.FullName}");
}
types.AsPublicImplementedInterfaces(serviceLifetime);
types.AsPublicImplementedInterfaces();
}
private IEnumerable<T> GetInstancesOfTypeInOurAssembly<T>() where T : class
{
return Assembly.GetAssembly(GetType()).GetTypes().Where(t => typeof(T).IsAssignableFrom(t) && !t.IsAbstract)
.Select(type => (T) Activator.CreateInstance(type, new object[0]));
return ExtensionManager.GetInstances<T>(assembly => assembly == Assembly.GetAssembly(GetType()));
}
}
}

View File

@ -1,57 +0,0 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
namespace BtcTransmuter.Abstractions.Extensions
{
public static class ControllerExtensions
{
public static bool IsApi(this Controller controller)
{
return controller.HttpContext.IsApi();
}
public static bool IsApi(this HttpContext context)
{
if (context.Items.TryGetValue("API", out var val))
{
return val is true;
}
return false;
}
public static void SetIsApi(this HttpContext context, bool val)
{
context.Items.TryAdd("API", val);
}
public static IActionResult ViewOrJson<T>(this Controller controller, string viewName, T payload)
{
if (controller.IsApi())
{
return controller.Json(payload);
}
return controller.View(viewName, payload);
}
public static IActionResult ViewOrJson<T>(this Controller controller, T payload)
{
if (controller.IsApi())
{
return controller.Json(payload);
}
return controller.View(payload);
}
public static IActionResult ViewOrBadRequest<T>(this Controller controller, T payload, bool usePayloadInBadRequest = false)
{
if (controller.IsApi())
{
return usePayloadInBadRequest ? controller.BadRequest(payload) : controller.BadRequest(controller.ModelState);
}
return controller.View(payload);
}
}
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace BtcTransmuter.Abstractions.Extensions
{
public static class EnumerableExtensions
{
public static HashSet<T> ToHashSet<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(source, comparer);
}
}
}

View File

@ -1,10 +0,0 @@
namespace BtcTransmuter.Abstractions.Extensions
{
public interface IExtension
{
string Name { get; }
string Version { get; }
string Description { get; }
string Authors { get; }
}
}

View File

@ -1,15 +0,0 @@
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace BtcTransmuter.Abstractions.Extensions
{
public static class ModelStateExtensions
{
public static void AddModelError<TModel>(this TModel source,
string name,
string message,
ModelStateDictionary modelState)
{
modelState.AddModelError(name, message);
}
}
}

View File

@ -1,16 +0,0 @@
using Microsoft.AspNetCore.Http;
namespace BtcTransmuter.Abstractions.Extensions
{
public static class RequestExtensions
{
public static string GetAbsoluteRoot(this HttpRequest request)
{
return string.Concat(
request.Scheme,
"://",
request.Host.ToUriComponent(),
request.PathBase.ToUriComponent());
}
}
}

View File

@ -1,17 +0,0 @@
using System;
namespace BtcTransmuter.Abstractions.Extensions
{
public static class StringExtensions
{
public static string TrimEnd(this string input, string suffixToRemove,
StringComparison comparisonType) {
if (input != null && suffixToRemove != null
&& input.EndsWith(suffixToRemove, comparisonType)) {
return input.Substring(0, input.Length - suffixToRemove.Length);
}
else return input;
}
}
}

View File

@ -19,15 +19,15 @@ namespace BtcTransmuter.Abstractions.ExternalServices
public abstract string Name { get; }
public abstract string Description { get; }
public abstract string ViewPartial { get; }
public abstract string ControllerName { get; }
protected abstract string ControllerName { get; }
public virtual T GetData()
public T GetData()
{
return _data.Get<T>();
}
public virtual void SetData(T data)
public void SetData(T data)
{
_data.Set(data);
}

View File

@ -18,8 +18,7 @@ namespace BtcTransmuter.Abstractions.ExternalServices
private readonly UserManager<User> _userManager;
private readonly IMemoryCache _memoryCache;
protected BaseExternalServiceController(IExternalServiceManager externalServiceManager,
UserManager<User> userManager,
protected BaseExternalServiceController(IExternalServiceManager externalServiceManager, UserManager<User> userManager,
IMemoryCache memoryCache)
{
_externalServiceManager = externalServiceManager;
@ -35,12 +34,11 @@ namespace BtcTransmuter.Abstractions.ExternalServices
{
return result.Error;
}
return View(await BuildViewModel(result.Data));
}
protected abstract Task<TViewModel> BuildViewModel(ExternalServiceData data);
protected abstract Task<(ExternalServiceData ToSave, TViewModel showViewModel)> BuildModel(
TViewModel viewModel, ExternalServiceData mainModel);
@ -69,7 +67,7 @@ namespace BtcTransmuter.Abstractions.ExternalServices
statusMessage = "Data updated"
});
}
private async Task<(IActionResult Error, ExternalServiceData Data )> GetExternalServiceData(string identifier)
{
ExternalServiceData data = null;
@ -82,7 +80,6 @@ namespace BtcTransmuter.Abstractions.ExternalServices
statusMessage = "Error:Data could not be found or data session expired"
}), null);
}
if (data.UserId != _userManager.GetUserId(User))
{
return (RedirectToAction("GetServices", "ExternalServices", new
@ -113,11 +110,5 @@ namespace BtcTransmuter.Abstractions.ExternalServices
return (null, data);
}
protected async Task<bool> IsAdmin()
{
var user = await _userManager.GetUserAsync(User);
return user!= null && await _userManager.IsInRoleAsync(user, "Admin");
}
}
}

View File

@ -1,78 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Linq.Dynamic.Core.CustomTypeProviders;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using BtcTransmuter.Abstractions.Extensions;
using Newtonsoft.Json;
namespace BtcTransmuter.Abstractions.Helpers
{
public class TransmuterInterpolationTypeProvider : DefaultDynamicLinqCustomTypeProvider
{
private readonly Type[] _types;
public TransmuterInterpolationTypeProvider(params Type[] types)
{
_types = types;
}
public override HashSet<Type> GetCustomTypes()
{
return _types.ToHashSet();
}
}
public sealed class InterpolationTypeProvider : DefaultDynamicLinqCustomTypeProvider {
private readonly IEnumerable<DefaultDynamicLinqCustomTypeProvider> _typeProviders;
public InterpolationTypeProvider(IEnumerable<TransmuterInterpolationTypeProvider> typeProviders)
{
_typeProviders = typeProviders;
ParsingConfig.Default.CustomTypeProvider = this;
}
public override HashSet<Type> GetCustomTypes()
{
return _typeProviders.SelectMany(provider => provider.GetCustomTypes()).ToHashSet();
}
}
public static class InterpolationHelper
{
/// <summary>
/// https://dotnetfiddle.net/MoqJFk
/// </summary>
public static string InterpolateString(string value, Dictionary<string, object> data)
{
try
{
var parameterExpressions =
data.Select(pair => Expression.Parameter(pair.Value.GetType(), pair.Key)).ToList();
return Regex.Replace(value, @"{{(.+?)}}",
match =>
{
var processed = match.Groups[1].Value;
try
{
var e = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null,
processed);
return (e.Compile().DynamicInvoke(data.Values.ToArray()) ?? "").ToString();
}
catch (Exception)
{
return processed;
}
});
}
catch (Exception)
{
return value;
}
}
}
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BtcTransmuter.Abstractions.Helpers
@ -11,14 +10,14 @@ namespace BtcTransmuter.Abstractions.Helpers
public static ICollection<ValidationResult> Validate<T>(string data)
{
try
{
var parsedData = JsonConvert.DeserializeObject<T>(data);
{
var parsedData = JObject.Parse(data).ToObject<T>();
var vc = new ValidationContext(parsedData);
var result = new List<ValidationResult>();
Validator.TryValidateObject(data, vc, result);
return result;
}
catch (Exception)
catch (Exception e)
{
return new List<ValidationResult>()
{
@ -26,5 +25,8 @@ namespace BtcTransmuter.Abstractions.Helpers
};
}
}
}
}

View File

@ -1,8 +0,0 @@
namespace BtcTransmuter.Abstractions.Models
{
public class OrderBy<T>
{
public T Field { get; set; }
public OrderDirection Direction { get; set; }
}
}

View File

@ -1,8 +0,0 @@
namespace BtcTransmuter.Abstractions.Models
{
public enum OrderDirection
{
Ascending,
Descending
}
}

View File

@ -1,4 +1,4 @@
using System.Collections;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using BtcTransmuter.Data;
@ -9,22 +9,15 @@ namespace BtcTransmuter.Abstractions.Recipes
public interface IRecipeManager
{
Task<IEnumerable<Recipe>> GetRecipes(RecipesQuery query);
Task<IEnumerable<RecipeInvocation>> GetRecipeInvocations(RecipeInvocationsQuery query);
Task AddOrUpdateRecipe(Recipe recipe);
Task AddOrUpdateRecipeTrigger(RecipeTrigger trigger);
Task AddOrUpdateRecipeTriggers(IEnumerable<RecipeTrigger> triggers);
Task AddOrUpdateRecipeAction(RecipeAction action);
Task AddRecipeActionGroup(RecipeActionGroup recipeActionGroup);
Task ReorderRecipeActionGroupActions(string recipeActionGroupId, Dictionary<string, int> actionsOrder);
Task RemoveRecipe(string id);
Task<Recipe> GetRecipe(string id, string userId = null);
Task AddRecipeInvocation(RecipeInvocation invocation);
Task RemoveRecipeAction(string recipeActionId);
Task RemoveRecipeTrigger(string recipeTriggerId);
Task RemoveRecipeActionGroup(string recipeActionGroupId);
Task<string> GetRecipeName(string recipeId);
Task<Recipe> CloneRecipe(string recipeId, bool enable, string newName = null);
}
}

View File

@ -1,18 +0,0 @@
using BtcTransmuter.Abstractions.Models;
namespace BtcTransmuter.Abstractions.Recipes
{
public class RecipeInvocationsQuery
{
public string RecipeId { get; set; }
public int Skip { get; set; }
public int Take { get; set; }
public OrderBy<RecipeInvocationsQueryOrderBy> OrderBy { get; set; }
public enum RecipeInvocationsQueryOrderBy
{
Timestamp
}
}
}

View File

@ -4,10 +4,8 @@ namespace BtcTransmuter.Abstractions.Recipes
{
public bool? Enabled { get; set; }
public string TriggerId { get; set; }
public string RecipeTriggerId { get; set; }
public string UserId { get; set; }
public string RecipeId { get; set; }
public bool IncludeRecipeInvocations { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using System.Threading.Tasks;
namespace BtcTransmuter.Abstractions.Settings
{
public interface ISettingsManager
{
Task<T> GetSettings<T>(string key);
Task SaveSettings<T>(string key,T settings);
}
}

View File

@ -1,5 +1,4 @@
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Data.Entities;
using Microsoft.AspNetCore.Authorization;
@ -15,18 +14,15 @@ namespace BtcTransmuter.Abstractions.Triggers
{
private readonly IRecipeManager _recipeManager;
private readonly UserManager<User> _userManager;
private readonly IExternalServiceManager _externalServiceManager;
private readonly IMemoryCache _memoryCache;
protected BaseTriggerController(
IRecipeManager recipeManager,
UserManager<User> userManager,
IMemoryCache memoryCache,
IExternalServiceManager externalServiceManager)
IMemoryCache memoryCache)
{
_recipeManager = recipeManager;
_userManager = userManager;
_externalServiceManager = externalServiceManager;
_memoryCache = memoryCache;
}
@ -61,16 +57,7 @@ namespace BtcTransmuter.Abstractions.Triggers
{
return View(modelResult.showViewModel);
}
if (!string.IsNullOrEmpty(modelResult.ToSave.ExternalServiceId))
{
var externalService= await _externalServiceManager.GetExternalServiceData(
modelResult.ToSave.ExternalServiceId,
GetUserId());
if (externalService == null)
{
return BadRequest("what you tryin to pull bro");
}
}
await _recipeManager.AddOrUpdateRecipeTrigger(modelResult.ToSave);
return RedirectToAction("EditRecipe", "Recipes", new
{

View File

@ -20,7 +20,7 @@ namespace BtcTransmuter.Abstractions.Triggers
public abstract string Name { get; }
public abstract string Description { get; }
public abstract string ViewPartial { get; }
public abstract string ControllerName { get; }
protected abstract string ControllerName { get; }
public Task<IActionResult> EditData(RecipeTrigger data)
{
@ -46,32 +46,27 @@ namespace BtcTransmuter.Abstractions.Triggers
TTriggerData triggerData, TTriggerParameters parameters);
public async Task<bool> IsTriggered(ITrigger trigger, RecipeTrigger recipeTrigger)
public Task<bool> IsTriggered(ITrigger trigger, RecipeTrigger recipeTrigger)
{
if (recipeTrigger.TriggerId != trigger.Id || trigger.Id != TriggerId)
{
return false;
return Task.FromResult(false);
}
var triggerData = await GetTriggerData(trigger);
var triggerData = trigger.Get<TTriggerData>();
if (typeof(IUseExternalService).IsAssignableFrom(typeof(TTriggerData)) &&
if (typeof(TTriggerParameters).IsAssignableFrom(typeof(IUseExternalService)) &&
((IUseExternalService) triggerData).ExternalServiceId != recipeTrigger.ExternalServiceId)
{
return false;
return Task.FromResult(false);
}
return await IsTriggered(trigger, recipeTrigger, triggerData, recipeTrigger.Get<TTriggerParameters>());
return IsTriggered(trigger, recipeTrigger, triggerData, recipeTrigger.Get<TTriggerParameters>());
}
public async Task<object> GetData(ITrigger trigger)
public Task<object> GetData(ITrigger trigger)
{
return await GetTriggerData(trigger);
}
public virtual Task<TTriggerData> GetTriggerData(ITrigger trigger)
{
return Task.FromResult(trigger.Get<TTriggerData>());
return Task.FromResult((object) trigger.Get<TTriggerData>());
}
public virtual Task AfterExecution(IEnumerable<Recipe> tupleItem1)

View File

@ -1,23 +0,0 @@
using System.Collections;
using System.ComponentModel.DataAnnotations;
namespace BtcTransmuter.Abstractions
{
public class EnsureMinimumElementsAttribute : ValidationAttribute
{
private readonly int _minElements;
public EnsureMinimumElementsAttribute(int minElements)
{
_minElements = minElements;
}
public override bool IsValid(object value)
{
if (value is IList list)
{
return list.Count >= _minElements;
}
return false;
}
}
}

View File

@ -1,6 +1,7 @@
using BtcTransmuter.Data.Encryption;
using System;
using System.Collections.Generic;
using System.Text;
using BtcTransmuter.Data.Entities;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
@ -9,65 +10,38 @@ namespace BtcTransmuter.Data
{
public class ApplicationDbContext : IdentityDbContext<User>
{
private readonly IDataProtectionProvider _dataProtectionProvider;
private readonly IBtcTransmuterOptions _btcTransmuterOptions;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IDataProtectionProvider dataProtectionProvider, IBtcTransmuterOptions btcTransmuterOptions)
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
_dataProtectionProvider = dataProtectionProvider;
_btcTransmuterOptions = btcTransmuterOptions;
}
public DbSet<ExternalServiceData> ExternalServices { get; set; }
public DbSet<Recipe> Recipes { get; set; }
public DbSet<RecipeActionGroup> RecipeActionGroups { get; set; }
public DbSet<RecipeInvocation> RecipeInvocations { get; set; }
public DbSet<RecipeTrigger> RecipeTriggers { get; set; }
public DbSet<RecipeAction> RecipeActions { get; set; }
public DbSet<Settings> Settings { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Recipe>()
.HasMany(l => l.RecipeActions)
.WithOne(action => action.Recipe)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Recipe>()
.HasOne(l => l.RecipeTrigger)
.WithOne(action => action.Recipe)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Recipe>()
.HasMany(l => l.RecipeInvocations)
.WithOne(action => action.Recipe)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Recipe>()
.HasMany(l => l.RecipeActionGroups)
.WithOne(action => action.Recipe)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<RecipeActionGroup>()
.HasMany(l => l.RecipeActions)
.WithOne(action => action.RecipeActionGroup)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<Settings>()
.HasIndex(settings => settings.Key)
.IsUnique();
if (_dataProtectionProvider != null && (_btcTransmuterOptions?.UseDatabaseColumnEncryption ?? false))
{
builder.AddEncryptionValueConvertersToDecoratedEncryptedColumns(_dataProtectionProvider.CreateProtector("ApplicationDbContext"));
}
}
}
public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
@ -75,7 +49,7 @@ namespace BtcTransmuter.Data
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlite("Data Source=mydb.db");
return new ApplicationDbContext(optionsBuilder.Options, null, null);
return new ApplicationDbContext(optionsBuilder.Options);
}
}
}
}

View File

@ -1,18 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<LangVersion>latest</LangVersion>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.2" />
<!--<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.6" />-->
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Migrations" />
</ItemGroup>
</Project>

View File

@ -1,8 +0,0 @@
using System;
namespace BtcTransmuter.Data.Encryption
{
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
sealed class EncryptedAttribute : Attribute
{ }
}

View File

@ -1,22 +0,0 @@
using System;
using System.Linq.Expressions;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BtcTransmuter.Data.Encryption
{
class EncryptedConverter : ValueConverter<string, string>
{
private static IDataProtector _dataProtector;
private static string encryptedMarker = "ENCRYPTED:";
public EncryptedConverter(IDataProtector dataProtector, ConverterMappingHints mappingHints = default)
: base(EncryptExpr, DecryptExpr, mappingHints)
{
_dataProtector = dataProtector;
}
static Expression<Func<string, string>> DecryptExpr = x => x.StartsWith(encryptedMarker)? _dataProtector.Unprotect(x.Substring(encryptedMarker.Length)): x;
static Expression<Func<string, string>> EncryptExpr = x => $"{encryptedMarker}{_dataProtector.Protect(x)}";
}
}

View File

@ -1,25 +0,0 @@
using System.Linq;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
namespace BtcTransmuter.Data.Encryption
{
public static class ModelBuilderExtensions
{
public static void AddEncryptionValueConvertersToDecoratedEncryptedColumns(this ModelBuilder modelBuilder,
IDataProtector dataProtector)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
var attributes = property.PropertyInfo.GetCustomAttributes(typeof(EncryptedAttribute), false);
if (attributes.Any())
{
property.SetValueConverter(new EncryptedConverter(dataProtector));
}
}
}
}
}
}

View File

@ -1,11 +1,11 @@
using System.ComponentModel.DataAnnotations.Schema;
using BtcTransmuter.Data.Encryption;
using BtcTransmuter.Data.Models;
namespace BtcTransmuter.Data.Entities
{
public abstract class BaseEntity :BaseIdEntity, IHasJsonData
public abstract class BaseEntity: IHasJsonData
{
[Encrypted] public string DataJson { get; set; }
public string Id { get; set; }
public string DataJson { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace BtcTransmuter.Data.Entities
{
public abstract class BaseIdEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
}
}

View File

@ -2,8 +2,9 @@ using System.Collections.Generic;
namespace BtcTransmuter.Data.Entities
{
public class Recipe : BaseIdEntity
public class Recipe
{
public string Id { get; set; }
public string UserId { get; set; }
public string Name { get; set; }
public bool Enabled { get; set; }
@ -11,14 +12,6 @@ namespace BtcTransmuter.Data.Entities
public RecipeTrigger RecipeTrigger { get; set; }
public List<RecipeAction> RecipeActions { get; set; } // executes all actions
public List<RecipeActionGroup>
RecipeActionGroups
{
get;
set;
} // executes actions in groups. Action in a group are executed syncrhonously and depending if the previous action executes
public List<RecipeInvocation> RecipeInvocations { get; set; } // log
}
}

View File

@ -5,19 +5,11 @@ namespace BtcTransmuter.Data.Entities
public class RecipeAction : BaseEntity
{
public string RecipeId { get; set; }
public string RecipeActionGroupId { get; set; }
public string ExternalServiceId { get; set; }
public string ActionId { get; set; }
public Recipe Recipe { get; set; }
public ExternalServiceData ExternalService { get; set; }
public RecipeActionGroup RecipeActionGroup { get; set; }
public int Order { get; set; } = 0;
public override string ToString()
{
return $"{ActionId} {(ExternalService == null ? string.Empty : $"using service {ExternalService.Name}")}";
}
public List<RecipeInvocation> RecipeInvocations { get; set; }
}
}

View File

@ -1,13 +0,0 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
namespace BtcTransmuter.Data.Entities
{
public class RecipeActionGroup : BaseIdEntity
{
public string RecipeId { get; set; }
public Recipe Recipe { get; set; }
public List<RecipeAction> RecipeActions { get; set; }
}
}

View File

@ -1,17 +1,17 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace BtcTransmuter.Data.Entities
{
public class RecipeInvocation: BaseIdEntity
public class RecipeInvocation
{
public string Id { get; set; }
public string RecipeId { get; set; }
public string RecipeAction { get; set; }
public string RecipeActionId { get; set; }
public string TriggerDataJson { get; set; }
public string ActionResult { get; set; }
public DateTime Timestamp { get; set; }
public Recipe Recipe { get; set; }
public RecipeAction RecipeAction { get; set; }
}
}

View File

@ -1,7 +0,0 @@
namespace BtcTransmuter.Data.Entities
{
public class Settings: BaseEntity
{
public string Key { get; set; }
}
}

View File

@ -1,26 +1,10 @@
using System.Collections.Generic;
using BtcTransmuter.Data.Encryption;
using BtcTransmuter.Data.Models;
using Microsoft.AspNetCore.Identity;
namespace BtcTransmuter.Data.Entities
{
public class User : IdentityUser, IHasJsonData
public class User : IdentityUser
{
public List<Recipe> Recipes { get; set; }
[Encrypted] public string DataJson { get; set; }
}
public class UserBlob
{
public bool BasicAuth { get; set; }
public BTCPayAuthDetails BTCPayAuthDetails { get; set; } = new BTCPayAuthDetails();
}
public class BTCPayAuthDetails
{
public string UserId { get; set; }
public string AccessToken { get; set; }
}
}

View File

@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BtcTransmuter.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190318105011_Initial")]
[Migration("20190218113432_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -69,37 +69,17 @@ namespace BtcTransmuter.Data.Migrations
b.Property<string>("ExternalServiceId");
b.Property<int>("Order");
b.Property<string>("RecipeActionGroupId");
b.Property<string>("RecipeId");
b.HasKey("Id");
b.HasIndex("ExternalServiceId");
b.HasIndex("RecipeActionGroupId");
b.HasIndex("RecipeId");
b.ToTable("RecipeActions");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeActionGroup", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("RecipeId");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("RecipeActionGroups");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeInvocation", b =>
{
b.Property<string>("Id")
@ -324,29 +304,16 @@ namespace BtcTransmuter.Data.Migrations
.WithMany("RecipeActions")
.HasForeignKey("ExternalServiceId");
b.HasOne("BtcTransmuter.Data.Entities.RecipeActionGroup", "RecipeActionGroup")
.WithMany("RecipeActions")
.HasForeignKey("RecipeActionGroupId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeActions")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeActionGroup", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeActionGroups")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeInvocation", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.RecipeAction", "RecipeAction")
.WithMany("RecipeInvocations")
.WithMany()
.HasForeignKey("RecipeActionId");
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")

View File

@ -195,17 +195,26 @@ namespace BtcTransmuter.Data.Migrations
});
migrationBuilder.CreateTable(
name: "RecipeActionGroups",
name: "RecipeActions",
columns: table => new
{
Id = table.Column<string>(nullable: false),
RecipeId = table.Column<string>(nullable: true)
DataJson = table.Column<string>(nullable: true),
RecipeId = table.Column<string>(nullable: true),
ExternalServiceId = table.Column<string>(nullable: true),
ActionId = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_RecipeActionGroups", x => x.Id);
table.PrimaryKey("PK_RecipeActions", x => x.Id);
table.ForeignKey(
name: "FK_RecipeActionGroups_Recipes_RecipeId",
name: "FK_RecipeActions_ExternalServices_ExternalServiceId",
column: x => x.ExternalServiceId,
principalTable: "ExternalServices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_RecipeActions_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id",
@ -239,41 +248,6 @@ namespace BtcTransmuter.Data.Migrations
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RecipeActions",
columns: table => new
{
Id = table.Column<string>(nullable: false),
DataJson = table.Column<string>(nullable: true),
RecipeId = table.Column<string>(nullable: true),
RecipeActionGroupId = table.Column<string>(nullable: true),
ExternalServiceId = table.Column<string>(nullable: true),
ActionId = table.Column<string>(nullable: true),
Order = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_RecipeActions", x => x.Id);
table.ForeignKey(
name: "FK_RecipeActions_ExternalServices_ExternalServiceId",
column: x => x.ExternalServiceId,
principalTable: "ExternalServices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_RecipeActions_RecipeActionGroups_RecipeActionGroupId",
column: x => x.RecipeActionGroupId,
principalTable: "RecipeActionGroups",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_RecipeActions_Recipes_RecipeId",
column: x => x.RecipeId,
principalTable: "Recipes",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RecipeInvocations",
columns: table => new
@ -344,21 +318,11 @@ namespace BtcTransmuter.Data.Migrations
table: "ExternalServices",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_RecipeActionGroups_RecipeId",
table: "RecipeActionGroups",
column: "RecipeId");
migrationBuilder.CreateIndex(
name: "IX_RecipeActions_ExternalServiceId",
table: "RecipeActions",
column: "ExternalServiceId");
migrationBuilder.CreateIndex(
name: "IX_RecipeActions_RecipeActionGroupId",
table: "RecipeActions",
column: "RecipeActionGroupId");
migrationBuilder.CreateIndex(
name: "IX_RecipeActions_RecipeId",
table: "RecipeActions",
@ -423,9 +387,6 @@ namespace BtcTransmuter.Data.Migrations
migrationBuilder.DropTable(
name: "ExternalServices");
migrationBuilder.DropTable(
name: "RecipeActionGroups");
migrationBuilder.DropTable(
name: "Recipes");

View File

@ -3,16 +3,14 @@ using System;
using BtcTransmuter.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace BtcTransmuter.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190331173724_FixCascade")]
partial class FixCascade
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
@ -69,37 +67,17 @@ namespace BtcTransmuter.Data.Migrations
b.Property<string>("ExternalServiceId");
b.Property<int>("Order");
b.Property<string>("RecipeActionGroupId");
b.Property<string>("RecipeId");
b.HasKey("Id");
b.HasIndex("ExternalServiceId");
b.HasIndex("RecipeActionGroupId");
b.HasIndex("RecipeId");
b.ToTable("RecipeActions");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeActionGroup", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("RecipeId");
b.HasKey("Id");
b.HasIndex("RecipeId");
b.ToTable("RecipeActionGroups");
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeInvocation", b =>
{
b.Property<string>("Id")
@ -324,31 +302,17 @@ namespace BtcTransmuter.Data.Migrations
.WithMany("RecipeActions")
.HasForeignKey("ExternalServiceId");
b.HasOne("BtcTransmuter.Data.Entities.RecipeActionGroup", "RecipeActionGroup")
.WithMany("RecipeActions")
.HasForeignKey("RecipeActionGroupId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeActions")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeActionGroup", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeActionGroups")
.HasForeignKey("RecipeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("BtcTransmuter.Data.Entities.RecipeInvocation", b =>
{
b.HasOne("BtcTransmuter.Data.Entities.RecipeAction", "RecipeAction")
.WithMany("RecipeInvocations")
.HasForeignKey("RecipeActionId")
.OnDelete(DeleteBehavior.Cascade);
.WithMany()
.HasForeignKey("RecipeActionId");
b.HasOne("BtcTransmuter.Data.Entities.Recipe", "Recipe")
.WithMany("RecipeInvocations")

View File

@ -1,4 +1,3 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BtcTransmuter.Data.Models
@ -7,12 +6,12 @@ namespace BtcTransmuter.Data.Models
{
public static T Get<T>(this IHasJsonData i)
{
return JsonConvert.DeserializeObject<T>(i.DataJson ?? "{}");
return JObject.Parse(i.DataJson ?? "{}").ToObject<T>();
}
public static void Set<T>(this IHasJsonData i, T data)
{
i.DataJson = JsonConvert.SerializeObject(data);
i.DataJson = JObject.FromObject(data).ToString();
}
}
}

View File

@ -1,166 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Triggers;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer;
using BtcTransmuter.Extension.BtcPayServer.HostedServices;
using BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged;
using BtcTransmuter.Tests.Base;
using Moq;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
using Xunit;
using Assert = Xunit.Assert;
namespace BtcTransmuter.Extension.BtcPayServer.Tests
{
public class BtcPayInvoiceWatcherHostedServiceTests : BaseTests
{
[Fact]
public async Task CanDetectInvoiceChanges()
{
var externalServiceManager = new Mock<IExternalServiceManager>();
var triggerDispatcher = new Mock<ITriggerDispatcher>();
var externalServices = new List<ExternalServiceData>()
{
new ExternalServiceData()
{
Type = BtcPayServerService.BtcPayServerServiceType,
Id = "A",
Name = "A",
DataJson = JsonConvert.SerializeObject(new BtcPayServerExternalServiceData())
}
};
var invoices = new List<BtcPayInvoice>()
{
new BtcPayInvoice()
{
Id = "A-A",
Status = Invoice.STATUS_NEW,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now,
}
};
ITrigger dispatchedTrigger = null;
triggerDispatcher.Setup(dispatcher => dispatcher.DispatchTrigger(It.IsAny<ITrigger>()))
.Callback<ITrigger>((x) => { dispatchedTrigger = x; }).Returns(Task.CompletedTask);
var mockBitPay = new TestBitpay();
mockBitPay.Invoices = () => invoices.ToArray();
var btcpayserviceMock = new Mock<BtcPayServerService>();
btcpayserviceMock.Setup(serverService => serverService.ConstructClient())
.Returns(() => mockBitPay);
btcpayserviceMock.Setup(serverService => serverService.CheckAccess()).ReturnsAsync(() => true);
btcpayserviceMock.Setup(serverService => serverService.GetData()).Returns(() => externalServices[0].Get<BtcPayServerExternalServiceData>());
externalServiceManager
.Setup(manager => manager.GetExternalServicesData(It.IsAny<ExternalServicesDataQuery>()))
.ReturnsAsync(() => externalServices);
var flag = 0;
externalServiceManager
.Setup(manager => manager.UpdateInternalData(It.IsAny<string>(), It.IsAny<object>()))
.Callback<string, object>((key, data) =>
{
externalServices[0].Set(data);
flag++;
externalServiceManager.Raise(manager => manager.ExternalServiceDataUpdated+=null, new UpdatedItem<ExternalServiceData>()
{
Item = externalServices[0],
Action = UpdatedItem<ExternalServiceData>.UpdateAction.Updated
});
})
.Returns(Task.CompletedTask);
var service =
new TestBtcPayInvoiceWatcherHostedService(externalServiceManager.Object, triggerDispatcher.Object);
service.ConvertData = data => btcpayserviceMock.Object;
await service.StartAsync(CancellationToken.None);
while (flag == 0)
{
await Task.Delay(200);
}
externalServiceManager.Verify(
manager => manager.GetExternalServicesData(It.IsAny<ExternalServicesDataQuery>()), Times.Once);
triggerDispatcher.Verify(dispatcher => dispatcher.DispatchTrigger(It.IsAny<ITrigger>()), Times.Once);
var invoice = Assert.IsType<InvoiceStatusChangedTrigger>(dispatchedTrigger).Data.Invoice;
Assert.Equal(invoices[0].Id, invoice.Id);
Assert.Equal(invoices[0].Status, invoice.Status);
invoices[0].Status = Invoice.STATUS_PAID;
while (flag == 1)
{
await Task.Delay(200);
}
triggerDispatcher.Verify(dispatcher => dispatcher.DispatchTrigger(It.IsAny<ITrigger>()), Times.Exactly(2));
invoice = Assert.IsType<InvoiceStatusChangedTrigger>(dispatchedTrigger).Data.Invoice;
Assert.Equal(invoices[0].Id, invoice.Id);
Assert.Equal(invoices[0].Status, invoice.Status);
}
public class TestBtcPayInvoiceWatcherHostedService : BtcPayInvoiceWatcherHostedService
{
public TestBtcPayInvoiceWatcherHostedService(IExternalServiceManager externalServiceManager,
ITriggerDispatcher triggerDispatcher) : base(externalServiceManager, triggerDispatcher)
{
}
public Func<ExternalServiceData, BtcPayServerService> ConvertData = null;
protected override BtcPayServerService GetServiceFromData(ExternalServiceData externalServiceData)
{
return ConvertData == null
? base.GetServiceFromData(externalServiceData)
: ConvertData(externalServiceData);
}
protected override TimeSpan CheckInterval { get; } = TimeSpan.FromSeconds(2);
}
public class TestBitpay : Bitpay
{
public TestBitpay(Key ecKey, Uri envUrl) : base(ecKey, envUrl)
{
}
public TestBitpay(): base(new Key(), new Uri("https://gozo.com"))
{
}
public Func<Invoice[]> Invoices;
public override Task<T[]> GetInvoicesAsync<T>(DateTime? dateStart = null, DateTime? dateEnd = null)
{
if (Invoices != null)
{
return Task.FromResult(Invoices.Invoke().Select(invoice => (T) invoice).ToArray());
}
return base.GetInvoicesAsync<T>(dateStart, dateEnd);
}
}
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.Linq;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer;
using BtcTransmuter.Tests.Base;
using Xunit;
namespace BtcTransmuter.Extension.BtcPayServer.Tests
{
public class
BtcPayServerServiceTests : BaseExternalServiceTest<BtcPayServerService, BtcPayServerExternalServiceData>
{
protected override BtcPayServerService GetExternalService(params object[] setupArgs)
{
if (setupArgs?.Any() ?? false)
{
return new BtcPayServerService((ExternalServiceData) setupArgs.First());
}
return new BtcPayServerService();
}
}
}

View File

@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Extension.BtcPayServer\BtcTransmuter.Extension.BtcPayServer.csproj" />
<ProjectReference Include="..\BtcTransmuter.Tests.Base\BtcTransmuter.Tests.Base.csproj" />
</ItemGroup>
</Project>

View File

@ -1,15 +0,0 @@
using System.Threading.Tasks;
using BtcTransmuter.Extension.BtcPayServer.Actions.GetInvoice;
using BtcTransmuter.Extension.BtcPayServer.HostedServices;
using BtcTransmuter.Tests.Base;
using Xunit;
namespace BtcTransmuter.Extension.BtcPayServer.Tests
{
public class
GetInvoiceDataActionHandlerTests : BaseActionTest<GetInvoiceDataActionHandler, GetInvoiceData, BtcPayInvoice>
{
}
}

View File

@ -1,12 +0,0 @@
using System.Collections.Generic;
using BtcTransmuter.Extension.BtcPayServer.Actions.GetPaymentsFromInvoice;
using BtcTransmuter.Tests.Base;
using NBitpayClient;
namespace BtcTransmuter.Extension.BtcPayServer.Tests
{
public class
GetPaymentsFromInvoiceDataActionHandlerTests : BaseActionTest<GetPaymentsFromInvoiceDataActionHandler, GetPaymentsFromInvoiceData, List<InvoicePaymentInfo>>
{
}
}

View File

@ -1,306 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Extension.BtcPayServer.HostedServices;
using BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged;
using BtcTransmuter.Tests.Base;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
using Assert = Xunit.Assert;
namespace BtcTransmuter.Extension.BtcPayServer.Tests
{
public class
InvoiceStatusChangedTriggerHandlerTests : BaseTriggerTest<InvoiceStatusChangedTriggerHandler,
InvoiceStatusChangedTriggerData,
InvoiceStatusChangedTriggerParameters>
{
[Fact]
public async Task IsTriggered_ExecutesCorrectly()
{
var handler = GetTriggerHandlerInstance();
//any status combination
var parameters = new InvoiceStatusChangedTriggerParameters()
{
Conditions = InvoiceStatusChangedController.AllowedStatuses.Select(item => new InvoiceStatusChangeCondition()
{
Status = item.Value,
ExceptionStatuses = InvoiceStatusChangedController.AllowedExceptionStatus.Select(item2 => item2.Value).ToList()
}).ToList()
};
Assert.True(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_NEW,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
//the external service id is not the same, if it triggers you are in deep shit
Assert.False(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "B",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_NEW,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
Assert.True(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_PAID,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
Assert.True(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_INVALID,
ExceptionStatus = new JValue(Invoice.EXSTATUS_PAID_PARTIAL),
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
//only paid status with no exception statuses
parameters.Conditions = new List<InvoiceStatusChangeCondition>()
{
new InvoiceStatusChangeCondition()
{
Status = Invoice.STATUS_PAID,
ExceptionStatuses = new List<string>()
{
Invoice.EXSTATUS_FALSE
}
}
};
Assert.True(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_PAID,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
Assert.False(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_PAID,
ExceptionStatus = Invoice.EXSTATUS_PAID_PARTIAL,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
Assert.False(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_NEW,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
parameters.Conditions = new List<InvoiceStatusChangeCondition>()
{
new InvoiceStatusChangeCondition()
{
Status = Invoice.STATUS_PAID,
ExceptionStatuses = new List<string>()
{
Invoice.EXSTATUS_FALSE
}
},
new InvoiceStatusChangeCondition()
{
Status = Invoice.STATUS_INVALID,
ExceptionStatuses = new List<string>()
{
"paidLate"
}
}
};
Assert.False(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_INVALID,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
Assert.False(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_PAID,
ExceptionStatus = Invoice.EXSTATUS_PAID_OVER,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
Assert.True(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_PAID,
ExceptionStatus = Invoice.EXSTATUS_FALSE,
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
Assert.True(await handler.IsTriggered(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
ExternalServiceId = "A",
Invoice = new BtcPayInvoice()
{
Status = BtcPayInvoice.STATUS_INVALID,
ExceptionStatus = "paidLate",
CurrentTime = DateTimeOffset.Now,
InvoiceTime = DateTimeOffset.Now,
ExpirationTime = DateTimeOffset.Now
}
}
},
new RecipeTrigger()
{
TriggerId = handler.TriggerId,
ExternalServiceId = "A",
DataJson = JsonConvert.SerializeObject(parameters)
}));
}
}
}

View File

@ -1,9 +0,0 @@
using BtcTransmuter.Abstractions.Actions;
namespace BtcTransmuter.Extension.BtcPayServer.Actions
{
public class BtcPayServerActionHandlerResult<T> : TypedActionHandlerResult<T>
{
public override string DataJson => NBitcoin.JsonConverters.Serializer.ToString(Data);
}
}

View File

@ -1,85 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Actions;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Caching.Memory;
namespace BtcTransmuter.Extension.BtcPayServer.Actions.GetInvoice
{
[Route("btcpayserver-plugin/actions/[controller]")]
[Authorize]
public class GetInvoiceController : BaseActionController<
GetInvoiceController.GetInvoiceViewModel,
GetInvoiceData>
{
private readonly IExternalServiceManager _externalServiceManager;
public GetInvoiceController(IMemoryCache memoryCache,
UserManager<User> userManager,
IRecipeManager recipeManager,
IExternalServiceManager externalServiceManager) : base(
memoryCache,
userManager, recipeManager, externalServiceManager)
{
_externalServiceManager = externalServiceManager;
}
protected override async Task<GetInvoiceViewModel> BuildViewModel(RecipeAction from)
{
var services = await _externalServiceManager.GetExternalServicesData(new ExternalServicesDataQuery
{
Type = new[] {BtcPayServerService.BtcPayServerServiceType},
UserId = GetUserId()
});
var fromData = from.Get<GetInvoiceData>();
return new GetInvoiceViewModel
{
RecipeId = from.RecipeId,
ExternalServiceId = from.ExternalServiceId,
ExternalServices = new SelectList(services, nameof(ExternalServiceData.Id),
nameof(ExternalServiceData.Name), from.ExternalServiceId),
InvoiceId = fromData.InvoiceId,
};
}
protected override async Task<(RecipeAction ToSave, GetInvoiceViewModel showViewModel)> BuildModel(
GetInvoiceViewModel viewModel, RecipeAction mainModel)
{
if (ModelState.IsValid)
{
mainModel.ExternalServiceId = viewModel.ExternalServiceId;
mainModel.Set<GetInvoiceData>(viewModel);
return (mainModel, null);
}
var services = await _externalServiceManager.GetExternalServicesData(new ExternalServicesDataQuery
{
Type = new[] {BtcPayServerService.BtcPayServerServiceType},
UserId = GetUserId()
});
viewModel.ExternalServices = new SelectList(services, nameof(ExternalServiceData.Id),
nameof(ExternalServiceData.Name), viewModel.ExternalServiceId);
return (null, viewModel);
}
public class GetInvoiceViewModel : GetInvoiceData, IActionViewModel, IUseExternalService
{
public string RecipeId { get; set; }
public string RecipeActionIdInGroupBeforeThisOne { get; set; }
public SelectList ExternalServices { get; set; }
public SelectList PaymentTypes { get; set; }
[Display(Name = "BtcPay External Service")]
[Required]
public string ExternalServiceId { get; set; }
}
}
}

View File

@ -1,10 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace BtcTransmuter.Extension.BtcPayServer.Actions.GetInvoice
{
public class GetInvoiceData
{
[Required]
public string InvoiceId { get; set; }
}
}

View File

@ -1,46 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Actions;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer;
using BtcTransmuter.Extension.BtcPayServer.HostedServices;
using BtcTransmuter.Extension.DynamicServices;
using NBitpayClient;
namespace BtcTransmuter.Extension.BtcPayServer.Actions.GetInvoice
{
public class GetInvoiceDataActionHandler : BaseActionHandler<GetInvoiceData, BtcPayInvoice>
{
public override string ActionId => "GetInvoice";
public override string Name => "Get BTCPay invoice";
public override string Description =>
"Get a specific btcpay invoice";
public override string ViewPartial => "ViewGetInvoiceAction";
public override string ControllerName => "GetInvoice";
public GetInvoiceDataActionHandler()
{
}
protected override async Task<TypedActionHandlerResult<BtcPayInvoice>> Execute(Dictionary<string, object> data, RecipeAction recipeAction,
GetInvoiceData actionData)
{
var externalService = await recipeAction.GetExternalService();
var service = new BtcPayServerService(externalService);
var invoiceId = InterpolateString(actionData.InvoiceId, data);
var client = service.ConstructClient();
var invoice = await client.GetInvoiceAsync<BtcPayInvoice>(invoiceId);
return new BtcPayServerActionHandlerResult<BtcPayInvoice>()
{
Executed = true,
TypedData = invoice,
Result = $"fetched invoice {invoiceId}"
};
}
}
}

View File

@ -1,98 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Actions;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Data.Models;
using BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.Caching.Memory;
namespace BtcTransmuter.Extension.BtcPayServer.Actions.GetPaymentsFromInvoice
{
[Route("btcpayserver-plugin/actions/[controller]")]
[Authorize]
public class GetPaymentsFromInvoiceController : BaseActionController<
GetPaymentsFromInvoiceController.GetPaymentsFromInvoiceViewModel,
GetPaymentsFromInvoiceData>
{
private readonly IExternalServiceManager _externalServiceManager;
public static readonly SelectListItem[] PaymentTypes =
{
new SelectListItem("Any", ""),
new SelectListItem("On-Chain", "BTCLike"),
new SelectListItem("Off-Chain", "LightningLike")
};
public GetPaymentsFromInvoiceController(IMemoryCache memoryCache,
UserManager<User> userManager,
IRecipeManager recipeManager,
IExternalServiceManager externalServiceManager) : base(
memoryCache,
userManager, recipeManager, externalServiceManager)
{
_externalServiceManager = externalServiceManager;
}
protected override async Task<GetPaymentsFromInvoiceViewModel> BuildViewModel(RecipeAction from)
{
var services = await _externalServiceManager.GetExternalServicesData(new ExternalServicesDataQuery
{
Type = new[] {BtcPayServerService.BtcPayServerServiceType},
UserId = GetUserId()
});
var fromData = from.Get<GetPaymentsFromInvoiceData>();
return new GetPaymentsFromInvoiceViewModel
{
RecipeId = from.RecipeId,
ExternalServiceId = from.ExternalServiceId,
ExternalServices = new SelectList(services, nameof(ExternalServiceData.Id),
nameof(ExternalServiceData.Name), from.ExternalServiceId),
PaymentTypes = new SelectList(PaymentTypes, nameof(SelectListItem.Value), nameof(SelectListItem.Text),
fromData.PaymentType),
PaymentType = fromData.PaymentType,
InvoiceId = fromData.InvoiceId,
CryptoCode = fromData.CryptoCode
};
}
protected override async Task<(RecipeAction ToSave, GetPaymentsFromInvoiceViewModel showViewModel)> BuildModel(
GetPaymentsFromInvoiceViewModel viewModel, RecipeAction mainModel)
{
if (ModelState.IsValid)
{
mainModel.ExternalServiceId = viewModel.ExternalServiceId;
mainModel.Set<GetPaymentsFromInvoiceData>(viewModel);
return (mainModel, null);
}
var services = await _externalServiceManager.GetExternalServicesData(new ExternalServicesDataQuery
{
Type = new[] {BtcPayServerService.BtcPayServerServiceType},
UserId = GetUserId()
});
viewModel.ExternalServices = new SelectList(services, nameof(ExternalServiceData.Id),
nameof(ExternalServiceData.Name), viewModel.ExternalServiceId);
viewModel.PaymentTypes = new SelectList(PaymentTypes, nameof(SelectListItem.Value),
nameof(SelectListItem.Text), viewModel.PaymentType);
return (null, viewModel);
}
public class GetPaymentsFromInvoiceViewModel : GetPaymentsFromInvoiceData, IActionViewModel, IUseExternalService
{
public string RecipeId { get; set; }
public string RecipeActionIdInGroupBeforeThisOne { get; set; }
public SelectList ExternalServices { get; set; }
public SelectList PaymentTypes { get; set; }
[Display(Name = "BtcPay External Service")]
[Required]
public string ExternalServiceId { get; set; }
}
}
}

View File

@ -1,13 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace BtcTransmuter.Extension.BtcPayServer.Actions.GetPaymentsFromInvoice
{
public class GetPaymentsFromInvoiceData
{
[Required]
public string InvoiceId { get; set; }
[Required]
public string CryptoCode { get; set; }
public string PaymentType { get; set; }
}
}

View File

@ -1,53 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Actions;
using BtcTransmuter.Data.Entities;
using BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer;
using BtcTransmuter.Extension.BtcPayServer.HostedServices;
using BtcTransmuter.Extension.DynamicServices;
using NBitpayClient;
namespace BtcTransmuter.Extension.BtcPayServer.Actions.GetPaymentsFromInvoice
{
public class GetPaymentsFromInvoiceDataActionHandler : BaseActionHandler<GetPaymentsFromInvoiceData, List<InvoicePaymentInfo>>
{
public override string ActionId => "GetPaymentsFromInvoice";
public override string Name => "Get payments on BTCPay invoice";
public override string Description =>
"Get the payments made to a specific btcpay invoice";
public override string ViewPartial => "ViewGetPaymentsFromInvoiceAction";
public override string ControllerName => "GetPaymentsFromInvoice";
public GetPaymentsFromInvoiceDataActionHandler()
{
}
protected override async Task<TypedActionHandlerResult<List<InvoicePaymentInfo>>> Execute(Dictionary<string, object> data, RecipeAction recipeAction,
GetPaymentsFromInvoiceData actionData)
{
var externalService = await recipeAction.GetExternalService();
var service = new BtcPayServerService(externalService);
var invoiceId = InterpolateString(actionData.InvoiceId, data);
var client = service.ConstructClient();
var invoice = await client.GetInvoiceAsync<BtcPayInvoice>(invoiceId);
var payments = invoice.CryptoInfo.Where(info => info.CryptoCode.Equals(actionData.CryptoCode))
.SelectMany(info => info.Payments)
.Where(x =>
string.IsNullOrEmpty(actionData.PaymentType) ||
x.PaymentType.Equals(actionData.PaymentType))
.ToList();
return new BtcPayServerActionHandlerResult<List<InvoicePaymentInfo>>()
{
Executed = true,
TypedData = payments,
Result = $"found {payments.Count} {actionData.CryptoCode} payments in invoice {invoiceId}"
};
}
}
}

View File

@ -1,10 +1,11 @@
using BtcTransmuter.Abstractions.Extensions;
namespace BtcTransmuter.Extension.BtcPayServer
{
public class BtcPayServerBtcTransmuterExtension : BtcTransmuterExtension
{
public override string Name => "BtcPayServer Plugin";
public override string Version => "0.0.1";
protected override int Priority => 0;
}
}

View File

@ -1,16 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
<PackageReference Include="NBitcoin" Version="5.0.40" />
<PackageReference Include="NBitpayClient" Version="1.0.0.38" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="NBitcoin" Version="4.1.1.82" />
<PackageReference Include="NBitpayClient" Version="1.0.0.32" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\BtcPayServer\EditData.cshtml" />
<Content Include="Views\InvoiceStatusChanged\EditData.cshtml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Styles\**;Views\**;Scripts\**" />
</ItemGroup>
</Project>

View File

@ -31,7 +31,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
{
Seed = clientData.Seed ?? new Mnemonic(Wordlist.English, WordCount.Twelve).ToString(),
Server = clientData.Server,
PairingUrl = await client.GetPairingUrl(data.Name),
PairingUrl = await client.GetPairingUrl(),
Paired = await client.CheckAccess()
};
}
@ -69,7 +69,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
{
Seed = serviceData.Seed ?? new Mnemonic(Wordlist.English, WordCount.Twelve).ToString(),
Server = serviceData.Server,
PairingUrl = await service.GetPairingUrl(mainModel.Name),
PairingUrl = await service.GetPairingUrl(),
Paired = await service.CheckAccess()
});
}
@ -79,7 +79,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
{
viewModel.Seed = viewModel.Seed ?? new Mnemonic(Wordlist.English, WordCount.Twelve).ToString();
service.SetData(viewModel);
viewModel.PairingUrl = await service.GetPairingUrl(mainModel.Name);
viewModel.PairingUrl = await service.GetPairingUrl();
viewModel.Paired = false;
if (!string.IsNullOrEmpty(viewModel.PairingCode))
{

View File

@ -6,8 +6,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
{
public class BtcPayServerExternalServiceData
{
[Required][Display(Name = "BtcPay Host Url")]
[Validation.Uri] public Uri Server { get; set; }
[Required] public Uri Server { get; set; }
public string Seed { get; set; }

View File

@ -19,7 +19,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
public override string Name => "BtcPayServer External Service";
public override string Description => "BtcPayServer External Service to be able to interact with its services";
public override string ViewPartial => "ViewBtcPayServerExternalService";
public override string ControllerName => "BtcPayServer";
protected override string ControllerName => "BtcPayServer";
public BtcPayServerService() : base()
@ -30,7 +30,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
{
}
public virtual Bitpay ConstructClient()
public Bitpay ConstructClient()
{
try
{
@ -45,20 +45,13 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
}
}
public virtual async Task<bool> CheckAccess()
public async Task<bool> CheckAccess()
{
try
{
var client = ConstructClient();
return client != null && await client.TestAccessAsync(Facade.Merchant);
}
catch (Exception)
{
return false;
}
var client = ConstructClient();
return client != null && await client.TestAccessAsync(Facade.Merchant);
}
public async Task<string> GetPairingUrl(string label)
public async Task<string> GetPairingUrl()
{
try
{
@ -69,14 +62,14 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
return null;
}
return (await client.RequestClientAuthorizationAsync(label, Facade.Merchant))
return (await client.RequestClientAuthorizationAsync("BtcTransmuter", Facade.Merchant))
.CreateLink(client.BaseUrl)
.ToString();
}
catch (Exception)
{
var data = GetData();
return Uri.TryCreate(data.Server, "api-tokens", out var result) ? result.ToString() : null;
return new Uri(data.Server, "api-tokens").ToString();
}
}
}

View File

@ -1,17 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
{
public class EditBtcPayServerDataViewModel : BtcPayServerExternalServiceData
{
public string Action { get; set; }
public string PairingUrl { get; set; }
[Display(Name =
"Pairing code from server initiated pairing. Alternatively, leave blank and click next to generate a pairing request url")]
public string PairingCode { get; set; }
public bool Paired { get; set; }
}
}

View File

@ -68,7 +68,5 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
{
return true;
}
}
}

View File

@ -19,7 +19,6 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
private readonly IExternalServiceManager _externalServiceManager;
private readonly ITriggerDispatcher _triggerDispatcher;
private ConcurrentDictionary<string, BtcPayServerService> _externalServices;
protected virtual TimeSpan CheckInterval { get; } = TimeSpan.FromMinutes(1);
public BtcPayInvoiceWatcherHostedService(IExternalServiceManager externalServiceManager,
ITriggerDispatcher triggerDispatcher)
@ -40,24 +39,19 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
_externalServices = new ConcurrentDictionary<string, BtcPayServerService>(
externalServices
.Select(service =>
new KeyValuePair<string, BtcPayServerService>(service.Id, GetServiceFromData(service))));
new KeyValuePair<string, BtcPayServerService>(service.Id, new BtcPayServerService(service))));
_externalServiceManager.ExternalServiceDataUpdated += ExternalServiceManagerOnExternalServiceUpdated;
_ = Loop(cancellationToken);
}
protected virtual BtcPayServerService GetServiceFromData(ExternalServiceData externalServiceData)
{
return new BtcPayServerService(externalServiceData);
}
private async Task Loop(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var tasks = _externalServices.Select(CheckInvoiceChangeInService);
await Task.WhenAll(tasks);
await Task.Delay(CheckInterval, cancellationToken);
await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken);
}
}
@ -65,46 +59,33 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
{
try
{
var key = pair.Key;
var service = pair.Value;
if (!await service.CheckAccess())
var (key, service) = pair;
if (!await service.CheckAccess())
{
return;
}
var data = service.GetData();
data.LastCheck = DateTime.Now;
if (data.MonitoredInvoiceStatuses == null)
{
data.MonitoredInvoiceStatuses = new Dictionary<string, string>();
}
var client = service.ConstructClient();
var invoices = await client.GetInvoicesAsync<BtcPayInvoice>(data.PairedDate);
foreach (var invoice in invoices)
{
//do not trigger on first run
if (data.LastCheck.HasValue)
{
return;
}
var data = service.GetData();
data.LastCheck = DateTime.Now;
if (data.MonitoredInvoiceStatuses == null)
{
data.MonitoredInvoiceStatuses = new Dictionary<string, string>();
}
var client = service.ConstructClient();
var invoices = await client.GetInvoicesAsync<BtcPayInvoice>(data.PairedDate, null);
foreach (var invoice in invoices)
{
//do not trigger on first run
if (data.LastCheck.HasValue)
if (data.MonitoredInvoiceStatuses.ContainsKey(invoice.Id))
{
if (data.MonitoredInvoiceStatuses.ContainsKey(invoice.Id))
if (data.MonitoredInvoiceStatuses[invoice.Id] != invoice.Status)
{
if (data.MonitoredInvoiceStatuses[invoice.Id] != invoice.Status)
{
_ = _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
Invoice = invoice,
ExternalServiceId = key
}
});
}
}
else
{
_ = _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
await _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
@ -114,13 +95,26 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
});
}
}
data.MonitoredInvoiceStatuses.AddOrReplace(invoice.Id, invoice.Status);
else
{
await _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
{
Data = new InvoiceStatusChangedTriggerData()
{
Invoice = invoice,
ExternalServiceId = key
}
});
}
}
service.SetData(data);
data.MonitoredInvoiceStatuses.AddOrReplace(invoice.Id, invoice.Status);
}
await _externalServiceManager.UpdateInternalData(key, data);
service.SetData( data);
await _externalServiceManager.UpdateInternalData(key, data);
}
catch (Exception e)
{
@ -143,13 +137,13 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
switch (e.Action)
{
case UpdatedItem<ExternalServiceData>.UpdateAction.Added:
_externalServices.TryAdd(e.Item.Id, GetServiceFromData(e.Item));
_externalServices.TryAdd(e.Item.Id, new BtcPayServerService(e.Item));
break;
case UpdatedItem<ExternalServiceData>.UpdateAction.Removed:
_externalServices.TryRemove(e.Item.Id, out var _);
break;
case UpdatedItem<ExternalServiceData>.UpdateAction.Updated:
_externalServices.TryUpdate(e.Item.Id, GetServiceFromData(e.Item), null);
_externalServices.TryUpdate(e.Item.Id, new BtcPayServerService(e.Item), null);
break;
default:
throw new ArgumentOutOfRangeException();

View File

@ -1,9 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Extensions;
using BtcTransmuter.Abstractions.ExternalServices;
using BtcTransmuter.Abstractions.Recipes;
using BtcTransmuter.Abstractions.Triggers;
@ -20,42 +16,26 @@ using NBitpayClient;
namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
{
[Authorize]
[Route("btcpayserver-plugin/triggers/[controller]")]
[Route("btcpayserver-plugin/triggers/invoice-status-changed")]
public class InvoiceStatusChangedController : BaseTriggerController<
InvoiceStatusChangedController.InvoiceStatusChangedTriggerViewModel, InvoiceStatusChangedTriggerParameters>
{
private readonly IExternalServiceManager _externalServiceManager;
public static readonly SelectListItem[] AllowedStatuses = new SelectListItem[]
private readonly SelectListItem[] AllowedStatuses = new SelectListItem[]
{
new SelectListItem() {Text = "Any Status", Value = null},
new SelectListItem() {Text = "New", Value = Invoice.STATUS_NEW},
new SelectListItem() {Text = "Paid", Value = Invoice.STATUS_PAID},
new SelectListItem() {Text = "Invalid", Value = Invoice.STATUS_INVALID},
new SelectListItem() {Text = "Confirmed", Value = Invoice.STATUS_CONFIRMED},
new SelectListItem() {Text = "Complete", Value = Invoice.STATUS_COMPLETE},
new SelectListItem() {Text = "Expired", Value = "expired"},
};
public static SelectListItem[] AllowedExceptionStatus = new SelectListItem[]
{
new SelectListItem() {Text = "None", Value = Invoice.EXSTATUS_FALSE},
new SelectListItem() {Text = "Paid partially", Value = Invoice.EXSTATUS_PAID_PARTIAL},
new SelectListItem() {Text = "Paid over", Value = Invoice.EXSTATUS_PAID_OVER},
new SelectListItem() {Text = "Paid late", Value = "paidLate"},
new SelectListItem() {Text = "Marked", Value = "marked"},
new SelectListItem() {Text = "Complete", Value = Invoice.STATUS_COMPLETE}
};
public static SelectListItem[] GetAvailableStatuses(string[] usedKeys)
{
return AllowedStatuses.Where(item => !usedKeys.Contains(item.Value)).ToArray();
}
public InvoiceStatusChangedController(IRecipeManager recipeManager, UserManager<User> userManager,
public InvoiceStatusChangedController(IRecipeManager recipeManager, UserManager<User> userManager,
IMemoryCache memoryCache, IExternalServiceManager externalServiceManager) : base(recipeManager, userManager,
memoryCache, externalServiceManager)
memoryCache)
{
_externalServiceManager = externalServiceManager;
}
@ -76,7 +56,8 @@ namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
nameof(ExternalServiceData.Name), data.ExternalServiceId),
RecipeId = data.RecipeId,
ExternalServiceId = data.ExternalServiceId,
Conditions = fromData.Conditions,
Status = fromData.Status,
Statuses = new SelectList(AllowedStatuses, "Value", "Text", fromData.Status),
};
}
@ -84,7 +65,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
BuildModel(
InvoiceStatusChangedTriggerViewModel viewModel, RecipeTrigger mainModel)
{
if (viewModel.Action != "EditData" || !ModelState.IsValid)
if (!ModelState.IsValid)
{
var btcPayServices = await _externalServiceManager.GetExternalServicesData(
new ExternalServicesDataQuery()
@ -92,29 +73,12 @@ namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
Type = new[] {BtcPayServerService.BtcPayServerServiceType},
UserId = GetUserId()
});
viewModel.Statuses = new SelectList(AllowedStatuses, "Value", "Text", viewModel.Status);
viewModel.ExternalServices = new SelectList(btcPayServices, nameof(ExternalServiceData.Id),
nameof(ExternalServiceData.Name), viewModel.ExternalServiceId);
if (viewModel.Action.StartsWith("add-condition", StringComparison.InvariantCultureIgnoreCase))
{
viewModel.Conditions.Add(new InvoiceStatusChangeCondition()
{
Status = GetAvailableStatuses(viewModel.Conditions.Select(condition => condition.Status).ToArray()).FirstOrDefault()?.Value?? Invoice.STATUS_NEW,
ExceptionStatuses = new List<string>()
{
Invoice.EXSTATUS_FALSE
}
});
}
if (viewModel.Action.StartsWith("remove-condition", StringComparison.InvariantCultureIgnoreCase))
{
var index = int.Parse(viewModel.Action.Substring(viewModel.Action.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1));
viewModel.Conditions.RemoveAt(index);
}
return (null, viewModel);
return (null, viewModel);
}
mainModel.ExternalServiceId = viewModel.ExternalServiceId;
@ -124,10 +88,10 @@ namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
public class InvoiceStatusChangedTriggerViewModel : InvoiceStatusChangedTriggerParameters
{
public string Action { get; set; }
public string RecipeId { get; set; }
public SelectList ExternalServices { get; set; }
[Required][Display(Name = "BtcPay Service")] public string ExternalServiceId { get; set; }
[Required] public string ExternalServiceId { get; set; }
public SelectList Statuses { get; set; }
}
}
}

View File

@ -2,13 +2,11 @@ using System;
using System.Threading.Tasks;
using BtcTransmuter.Abstractions.Triggers;
using BtcTransmuter.Data.Entities;
using NBitpayClient;
using Newtonsoft.Json.Linq;
namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
{
public class InvoiceStatusChangedTriggerHandler : BaseTriggerHandler<InvoiceStatusChangedTriggerData,
InvoiceStatusChangedTriggerParameters>
InvoiceStatusChangedTriggerParameters>
{
public override string TriggerId => new InvoiceStatusChangedTrigger().Id;
public override string Name => "BtcPayServer Invoice Status Change";
@ -17,30 +15,19 @@ namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
"Trigger a recipe by detecting a status change of an invoice in a btcpay external service.";
public override string ViewPartial => "ViewInvoiceStatusChangedTrigger";
public override string ControllerName => "InvoiceStatusChanged";
protected override string ControllerName => "InvoiceStatusChanged";
protected override Task<bool> IsTriggered(ITrigger trigger, RecipeTrigger recipeTrigger,
InvoiceStatusChangedTriggerData triggerData,
InvoiceStatusChangedTriggerParameters parameters)
{
var exceptionStatus =Invoice.EXSTATUS_FALSE;
if (triggerData.Invoice.ExceptionStatus.Type == JTokenType.String)
if (string.IsNullOrEmpty(parameters.Status))
{
exceptionStatus = triggerData.Invoice.ExceptionStatus.Value<string>();
}
var status = triggerData.Invoice.Status;
foreach (var condition in parameters.Conditions)
{
if (condition.Status.Equals(status, StringComparison.InvariantCultureIgnoreCase) &&
condition.ExceptionStatuses.Contains(exceptionStatus))
{
return Task.FromResult(true);
}
return Task.FromResult(true);
}
return Task.FromResult(false);
return Task.FromResult(triggerData.Invoice.Status.Equals(parameters.Status,
StringComparison.InvariantCultureIgnoreCase));
}
}
}

View File

@ -1,22 +1,9 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using BtcTransmuter.Abstractions;
namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
{
public class InvoiceStatusChangedTriggerParameters
{
public List<InvoiceStatusChangeCondition> Conditions { get; set; } = new List<InvoiceStatusChangeCondition>();
}
public class InvoiceStatusChangeCondition
{
[Required]
public string Status { get; set; }
[Display(Name = "Additional Statuses")]
[Required]
[EnsureMinimumElements(1, ErrorMessage = "At least one additional status is required")]
public List<string> ExceptionStatuses { get; set; }
public string Status { get; set; }
}
}

View File

@ -1,22 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
namespace BtcTransmuter.Extension.BtcPayServer.Validation
{
public class UriAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var str = value == null ? null : Convert.ToString(value, CultureInfo.InvariantCulture);
Uri uri;
bool valid = string.IsNullOrWhiteSpace(str) || Uri.TryCreate(str, UriKind.Absolute, out uri);
if (!valid)
{
return new ValidationResult(ErrorMessage);
}
return ValidationResult.Success;
}
}
}

View File

@ -9,14 +9,13 @@
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Server" class="control-label"></label>
<input asp-for="Server" class="form-control"/>
<span asp-validation-for="Server" class="text-danger"></span>
</div>
@if (!Model.Paired)
{
<div class="form-group">
<label asp-for="Server" class="control-label"></label>
<input asp-for="Server" class="form-control"/>
<span asp-validation-for="Server" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PairingCode" class="control-label"></label>
<input asp-for="PairingCode" class="form-control"/>
@ -25,17 +24,12 @@
if (!string.IsNullOrEmpty(Model.PairingUrl))
{
<span>Authorize Client </span>
<span>Authorize Client </span>
<a href="@Model.PairingUrl" target="_blank"> @Model.PairingUrl</a>
}
}
else
{
<div class="form-group">
<label asp-for="Server" class="control-label"></label>
<input asp-for="Server" class="form-control" readonly="readonly"/>
<span asp-validation-for="Server" class="text-danger"></span>
</div>
<div class="alert alert-success"> Currently Paired</div>
}
<input type="hidden" name="Seed" value="@Model.Seed"/>
@ -45,10 +39,7 @@
{
<button type="submit" value="unpair" name="action" class="btn btn-danger">Unpair</button>
}
else
{
<button type="submit" class="btn btn-primary">Save</button>
}
<button type="submit" class="btn btn-primary">Save</button>
<a asp-action="GetServices" asp-controller="ExternalServices" class="btn btn-secondary">Back to recipe</a>
</div>
</form>

View File

@ -1,36 +0,0 @@
@using Microsoft.EntityFrameworkCore.Internal
@model BtcTransmuter.Extension.BtcPayServer.Actions.GetInvoice.GetInvoiceController.GetInvoiceViewModel
@{
ViewData["Title"] = "Edit Get Btcpay Invoice Action";
}
<h2>@ViewData["Title"]</h2>
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ExternalServiceId" class="control-label"></label>
<select asp-for="ExternalServiceId" asp-items="Model.ExternalServices" class="form-control"></select>
<span asp-validation-for="ExternalServiceId" class="text-danger"></span>
@if (!Model.ExternalServices.Items.Any())
{
<span class="text-danger">There are no BtcPay external services available to create this action. <a asp-action="CreateExternalService" asp-controller="ExternalServices">Create one</a></span>
}
</div>
<div class="form-group">
<label asp-for="InvoiceId" class="control-label"></label>
<input asp-for="InvoiceId" class="form-control" />
<span asp-validation-for="InvoiceId" class="text-danger"></span>
</div>
<input type="hidden" asp-for="RecipeId"/>
<div class="mt-2">
<button type="submit" class="btn btn-primary">Save</button>
<a asp-action="EditRecipe" asp-controller="Recipes" class="btn btn-secondary" asp-route-id="@Model.RecipeId">Back to recipe</a>
</div>
</form>
@await Component.InvokeAsync("RecipeActionFooter", new
{
recipeId = @Model.RecipeId,
recipeActionIdInGroupBeforeThisOne = @Model.RecipeActionIdInGroupBeforeThisOne
})

View File

@ -1,46 +0,0 @@
@using Microsoft.EntityFrameworkCore.Internal
@model BtcTransmuter.Extension.BtcPayServer.Actions.GetPaymentsFromInvoice.GetPaymentsFromInvoiceController.GetPaymentsFromInvoiceViewModel
@{
ViewData["Title"] = "Edit Get Payment for Btcpay Invoice Action";
}
<h2>@ViewData["Title"]</h2>
<form method="post">
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="ExternalServiceId" class="control-label"></label>
<select asp-for="ExternalServiceId" asp-items="Model.ExternalServices" class="form-control"></select>
<span asp-validation-for="ExternalServiceId" class="text-danger"></span>
@if (!Model.ExternalServices.Items.Any())
{
<span class="text-danger">There are no BtcPay external services available to create this action. <a asp-action="CreateExternalService" asp-controller="ExternalServices">Create one</a></span>
}
</div>
<div class="form-group">
<label asp-for="CryptoCode" class="control-label"></label>
<input asp-for="CryptoCode" class="form-control" />
<span asp-validation-for="CryptoCode" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="InvoiceId" class="control-label"></label>
<input asp-for="InvoiceId" class="form-control" />
<span asp-validation-for="InvoiceId" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PaymentType" class="control-label"></label>
<select asp-for="PaymentType" asp-items="Model.PaymentTypes" class="form-control"></select>
<span asp-validation-for="PaymentType" class="text-danger"></span>
</div>
<input type="hidden" asp-for="RecipeId"/>
<div class="mt-2">
<button type="submit" class="btn btn-primary">Save</button>
<a asp-action="EditRecipe" asp-controller="Recipes" class="btn btn-secondary" asp-route-id="@Model.RecipeId">Back to recipe</a>
</div>
</form>
@await Component.InvokeAsync("RecipeActionFooter", new
{
recipeId = @Model.RecipeId,
recipeActionIdInGroupBeforeThisOne = @Model.RecipeActionIdInGroupBeforeThisOne
})

View File

@ -1,4 +1,3 @@
@using Microsoft.EntityFrameworkCore.Internal
@model BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged.InvoiceStatusChangedController.InvoiceStatusChangedTriggerViewModel
@ -15,52 +14,15 @@
<label asp-for="ExternalServiceId" class="control-label"></label>
<select asp-for="ExternalServiceId" asp-items="Model.ExternalServices" class="form-control"></select>
<span asp-validation-for="ExternalServiceId" class="text-danger"></span>
@if (!Model.ExternalServices.Items.Any())
{
<span class="text-danger">There are no BtcPay external services available to create this action. <a asp-action="CreateExternalService" asp-controller="ExternalServices">Create one</a></span>
}
</div>
<div class="list-group mb-2">
<div class="list-group-item ">
<h5 class="mb-1">Status Conditions</h5>
</div>
@for (var index = 0; index < Model.Conditions.Count; index++)
{
var statusCondition = Model.Conditions[index];
<div class="list-group-item justify-content-between align-items-center">
<div class="row">
<div class="form-group col-sm-12 col-md-6">
<label asp-for="Conditions[index].Status" class="control-label"></label>
<select asp-for="Conditions[index].Status"
asp-items="@(new SelectList(BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged.InvoiceStatusChangedController.AllowedStatuses, "Value", "Text", statusCondition.Status))" class="form-control">
</select>
<span asp-validation-for="Conditions[index].Status" class="text-danger"></span>
</div>
<div class="form-group col-sm-12 col-md-6">
<label asp-for="Conditions[index].ExceptionStatuses" class="control-label"></label>
<select asp-for="Conditions[index].ExceptionStatuses"
asp-items="@(new SelectList(BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged.InvoiceStatusChangedController.AllowedExceptionStatus, "Value", "Text", statusCondition.ExceptionStatuses))"
multiple="multiple"
class="form-control">
</select>
<span asp-validation-for="Conditions[index].ExceptionStatuses" class="text-danger"></span>
</div>
</div>
<div>
<button type="submit" name="action" value="@($"remove-condition:{index}")" class="btn btn-danger">Remove</button>
</div>
</div>
}
<div class="list-group-item ">
<button type="submit" name="action" value="add-condition" class="btn btn-secondary">Add</button>
</div>
<div class="form-group">
<label asp-for="Status" class="control-label"></label>
<select asp-for="Status" asp-items="Model.Statuses" class="form-control"></select>
<span asp-validation-for="Status" class="text-danger"></span>
</div>
<input type="hidden" asp-for="RecipeId" />
<div class="mt-2">
<button type="submit" name="" value="" class="btn btn-primary">Save</button>
<button type="submit" class="btn btn-primary">Save</button>
<a asp-action="EditRecipe" asp-controller="Recipes" class="btn btn-secondary" asp-route-id="@Model.RecipeId">Back to recipe</a>
</div>
</form>

View File

@ -1,9 +0,0 @@
@using BtcTransmuter.Data.Models
@using BtcTransmuter.Extension.BtcPayServer.Actions.GetInvoice
@model BtcTransmuter.Data.Entities.RecipeAction
@{
var data = Model.Get<GetInvoiceData>();
}
<div>
Get BtcPay invoice <kbd>@data.InvoiceId</kbd>
</div>

View File

@ -1,9 +0,0 @@
@using BtcTransmuter.Data.Models
@using BtcTransmuter.Extension.BtcPayServer.Actions.GetPaymentsFromInvoice
@model BtcTransmuter.Data.Entities.RecipeAction
@{
var data = Model.Get<GetPaymentsFromInvoiceData>();
}
<div>
Get <kbd>@data.CryptoCode @data.PaymentType</kbd>payments of BTCPay invoice <kbd>@data.InvoiceId</kbd>
</div>

View File

@ -6,15 +6,10 @@
}
<div>
Btcpayserver Invoice Status Change to
<kbd>
@if (data.Conditions.Any())
{
@string.Join(" or ", data.Conditions.Select(condition => $"{condition.Status}({string.Join(" or ", condition.ExceptionStatuses)})"))
}
else
{
<span>Not configured</span>
}
</kbd>
Btcpayserver Invoice Status Change
@if (!string.IsNullOrEmpty(data.Status))
{
<span>to @data.Status</span>
}
</div>

Some files were not shown because too many files have changed in this diff Show More