Compare commits
429 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb8d10a0b9 | ||
|
|
b19665152d | ||
|
|
561c676758 | ||
|
|
6699e73ed6 | ||
|
|
c5dfbf336e | ||
|
|
e74a996424 | ||
|
|
96c3747ad9 | ||
|
|
c7fe58200f | ||
|
|
8e3051e61b | ||
|
|
d84d4bdf7f | ||
|
|
003c261909 | ||
|
|
4dc82b81c5 | ||
|
|
d36ead30a7 | ||
|
|
4cb2a23900 | ||
|
|
b3faa84cf3 | ||
|
|
b413816797 | ||
|
|
048b59c261 | ||
|
|
7de6647e65 | ||
|
|
f4a837f36f | ||
|
|
140d2c206a | ||
|
|
50c1651cba | ||
|
|
a497084b34 | ||
|
|
051a27fd86 | ||
|
|
124982bd94 | ||
|
|
97f682115c | ||
|
|
39bd13bb25 | ||
|
|
2ef1e353e7 | ||
|
|
8334969f93 | ||
|
|
21ecf986f1 | ||
|
|
cb6fa66300 | ||
|
|
32f21e5306 | ||
|
|
5d0b771a36 | ||
|
|
435ebf8909 | ||
|
|
323f7e8481 | ||
|
|
2d7911bc95 | ||
|
|
ce7a70bb99 | ||
|
|
9b4fb02241 | ||
|
|
aa68a90224 | ||
|
|
236f416fc6 | ||
|
|
36fbd57209 | ||
|
|
28c2bac049 | ||
|
|
2c6ace2a95 | ||
|
|
a9d5a2fadb | ||
|
|
215d311113 | ||
|
|
e598cc10d2 | ||
|
|
25383e196b | ||
|
|
d5c84fa491 | ||
|
|
05b64186fb | ||
|
|
a75c275c10 | ||
|
|
1ca2119138 | ||
|
|
0138d9712b | ||
|
|
19db0dff46 | ||
|
|
f21a4ac701 | ||
|
|
0295dd1e87 | ||
|
|
1293f03182 | ||
|
|
d56356d2f3 | ||
|
|
ded2313463 | ||
|
|
e7c48e2db7 | ||
|
|
ddfc647300 | ||
|
|
5902da4212 | ||
|
|
774007e5c7 | ||
|
|
c47efa0b43 | ||
|
|
c8fee0e18f | ||
|
|
c3b73c7e4d | ||
|
|
0e7a7a5d70 | ||
|
|
08a3329eff | ||
|
|
5b29c62fc6 | ||
|
|
8ad099bcce | ||
|
|
2fba3da43e | ||
|
|
9649294bbf | ||
|
|
c555df3054 | ||
|
|
78ca88b030 | ||
|
|
7ffaa89f55 | ||
|
|
d80adaada3 | ||
|
|
2c7f7ef780 | ||
|
|
e1d82f0350 | ||
|
|
4dd0b4450c | ||
|
|
e258d6b19f | ||
|
|
be14704843 | ||
|
|
4744d574e4 | ||
|
|
a085305b4b | ||
|
|
f09094087c | ||
|
|
9b950cf46d | ||
|
|
bebefb5f48 | ||
|
|
079b67f421 | ||
|
|
4efeddf080 | ||
|
|
951ae61b25 | ||
|
|
58946b1427 | ||
|
|
a2eb4d0194 | ||
|
|
33fde4b51f | ||
|
|
a82ea4221b | ||
|
|
19858588db | ||
|
|
de1a6a0adc | ||
|
|
f55e390859 | ||
|
|
ba945afb3a | ||
|
|
9efb53b93a | ||
|
|
c108c4e9b2 | ||
|
|
f705b2c187 | ||
|
|
875248fc23 | ||
|
|
d05a1a5c3c | ||
|
|
a91aa41173 | ||
|
|
987b2d4169 | ||
|
|
41672156c4 | ||
|
|
eb61e7c08f | ||
|
|
6ee85be74a | ||
|
|
7589e94202 | ||
|
|
df946a441a | ||
|
|
80c4ad5189 | ||
|
|
9f64f17b49 | ||
|
|
205151a83d | ||
|
|
12c599e5cb | ||
|
|
ba491bfccd | ||
|
|
9d9c61c9f2 | ||
|
|
b067106fcf | ||
|
|
fc6158db3b | ||
|
|
6bc7024244 | ||
|
|
67f39bf73d | ||
|
|
cf06bb1a7c | ||
|
|
d653113492 | ||
|
|
61a8daa1b5 | ||
|
|
834d87e8ae | ||
|
|
c85c03e8e7 | ||
|
|
5464d508e4 | ||
|
|
037d280c6b | ||
|
|
739d303956 | ||
|
|
b725d48860 | ||
|
|
c357f67b46 | ||
|
|
8dcc42d867 | ||
|
|
6d7b95e089 | ||
|
|
85924bd786 | ||
|
|
4a8c73e805 | ||
|
|
5a2586e597 | ||
|
|
a65ef7e340 | ||
|
|
0b58b61b07 | ||
|
|
cd207bc0dc | ||
|
|
9ef7b0b2b5 | ||
|
|
b725299b1d | ||
|
|
12c1ea59b3 | ||
|
|
62d6494cb0 | ||
|
|
2e46509b10 | ||
|
|
b9979f7bfd | ||
|
|
48b2084f54 | ||
|
|
52f6f5c4ba | ||
|
|
240ff51c7e | ||
|
|
ea6cfaab25 | ||
|
|
40d561f199 | ||
|
|
9d9b099a31 | ||
|
|
bb83f20fb2 | ||
|
|
6e8db6282a | ||
|
|
1e3b66ca6d | ||
|
|
f3f9a07cb0 | ||
|
|
b20b149b1c | ||
|
|
d9f672804c | ||
|
|
b2e5b14dd6 | ||
|
|
6638105e69 | ||
|
|
36878ec7c0 | ||
|
|
afa340ccb8 | ||
|
|
0c8d9bd136 | ||
|
|
88786b5fe5 | ||
|
|
f9f1d2d2f3 | ||
|
|
090bbd464a | ||
|
|
061fab086b | ||
|
|
d5ec6f7e0d | ||
|
|
dff978f556 | ||
|
|
fda441854f | ||
|
|
c725444347 | ||
|
|
a90a44cb19 | ||
|
|
d0442693b2 | ||
|
|
cfc6e95012 | ||
|
|
58cbbfa5f8 | ||
|
|
7a755d356b | ||
|
|
ee63b10f96 | ||
|
|
4e3b832395 | ||
|
|
e44094ceb1 | ||
|
|
d6cd796ae5 | ||
|
|
d043f0bf88 | ||
|
|
f66e971c0a | ||
|
|
a5a34f3681 | ||
|
|
f41f6efe4c | ||
|
|
5adc78eede | ||
|
|
5f3edaa0eb | ||
|
|
1a9f972dab | ||
|
|
c0c6083069 | ||
|
|
afc65f0f15 | ||
|
|
5118869219 | ||
|
|
bd937ae9e3 | ||
|
|
2f5f53531e | ||
|
|
1921f19e66 | ||
|
|
87dd78f3ab | ||
|
|
8bad5e1105 | ||
|
|
a17697a011 | ||
|
|
dcd79d104b | ||
|
|
3b92f4b4ed | ||
|
|
b996143abc | ||
|
|
07e0ccd284 | ||
|
|
0549ecd94c | ||
|
|
cc6065d7df | ||
|
|
832eec1400 | ||
|
|
651b2bf181 | ||
|
|
2e626e448b | ||
|
|
2b66b0b26b | ||
|
|
dca7bd02c5 | ||
|
|
9bd4864e4a | ||
|
|
94377e964c | ||
|
|
2cb8e9c02b | ||
|
|
d8e43af4a4 | ||
|
|
76d52a4be2 | ||
|
|
51f7724f3c | ||
|
|
fe37f28713 | ||
|
|
17684a2a44 | ||
|
|
2b284733a6 | ||
|
|
19babb809b | ||
|
|
624dd99654 | ||
|
|
110d287e94 | ||
|
|
caee4b70f5 | ||
|
|
104c2074df | ||
|
|
53ea6b9b76 | ||
|
|
6da6edd27f | ||
|
|
0100da72f0 | ||
|
|
fb75d110f5 | ||
|
|
e02637c850 | ||
|
|
80d753db54 | ||
|
|
3340ca6cde | ||
|
|
a7432b868a | ||
|
|
caf0f6b691 | ||
|
|
409f3a01c0 | ||
|
|
797863aa79 | ||
|
|
6314829956 | ||
|
|
4b60896aa4 | ||
|
|
5486680082 | ||
|
|
ae5895b4ab | ||
|
|
9d6ead2d82 | ||
|
|
e0f9348b5f | ||
|
|
ebd8d94663 | ||
|
|
a10fdf5c28 | ||
|
|
6890e272cb | ||
|
|
da8a2cea85 | ||
|
|
01d994fe85 | ||
|
|
1ae11a2c8d | ||
|
|
6d0d1c1658 | ||
|
|
c499f60e11 | ||
|
|
fed70fbd3b | ||
|
|
8f1bc8bec9 | ||
|
|
d07b0fec89 | ||
|
|
ffb5b3d937 | ||
|
|
d474983185 | ||
|
|
917035cf16 | ||
|
|
1a50156c96 | ||
|
|
2936db7e1f | ||
|
|
e75cb999fb | ||
|
|
c548c0721e | ||
|
|
4bdfc9d89c | ||
|
|
e097e3619e | ||
|
|
0c82b52a70 | ||
|
|
27453f7010 | ||
|
|
cdd4099f8d | ||
|
|
ce1facbb16 | ||
|
|
e1c6a3f295 | ||
|
|
0be41bb278 | ||
|
|
11a3512372 | ||
|
|
a7b522f3eb | ||
|
|
52c39903a7 | ||
|
|
7d05b1c224 | ||
|
|
29e43daaab | ||
|
|
a8d2de67c5 | ||
|
|
ffab3f8ef5 | ||
|
|
9ac8101a9b | ||
|
|
a9eaa83a3b | ||
|
|
c3b7fd8ecc | ||
|
|
31a95c3815 | ||
|
|
193c544c5e | ||
|
|
033dbd84e6 | ||
|
|
9cb0a7f08a | ||
|
|
686b6b7639 | ||
|
|
e598d5aa21 | ||
|
|
c291dee93c | ||
|
|
a875cebba6 | ||
|
|
22892b3de7 | ||
|
|
90c8fce73e | ||
|
|
d76f88a85d | ||
|
|
9c9eb05fef | ||
|
|
911ea78395 | ||
|
|
8eaf9a3b51 | ||
|
|
cf6f45e700 | ||
|
|
46ae42c52d | ||
|
|
980c18bb65 | ||
|
|
3bc2ca969f | ||
|
|
73ad01dab2 | ||
|
|
b4b93a84ee | ||
|
|
4b4a6491e9 | ||
|
|
083496678a | ||
|
|
ddaeebb708 | ||
|
|
260dfa5636 | ||
|
|
b7b09e3fbb | ||
|
|
c0cccad5de | ||
|
|
d895b2fc76 | ||
|
|
acf5e78afe | ||
|
|
05cf5c3feb | ||
|
|
e7c64f90b3 | ||
|
|
b12ebb5ad0 | ||
|
|
004b3a5466 | ||
|
|
c43dddbed7 | ||
|
|
70853a36c6 | ||
|
|
2f3c479fd2 | ||
|
|
4f9c7b54e7 | ||
|
|
e9e5f9ab4a | ||
|
|
5e212c7ef5 | ||
|
|
d4ef1a46b7 | ||
|
|
f3a25735de | ||
|
|
ba274e126c | ||
|
|
b032ca55b4 | ||
|
|
59b79645b7 | ||
|
|
9fd2875431 | ||
|
|
d1902741bb | ||
|
|
77b171984c | ||
|
|
71699f01a1 | ||
|
|
b46bfdad9c | ||
|
|
30bfd4627a | ||
|
|
efe08046a0 | ||
|
|
2333ddcd3a | ||
|
|
632a5d531e | ||
|
|
29725809aa | ||
|
|
e11925ece5 | ||
|
|
78fb747ed5 | ||
|
|
822e6b2222 | ||
|
|
eee2491f06 | ||
|
|
ed8088d821 | ||
|
|
c2554a5315 | ||
|
|
245019f5e7 | ||
|
|
ca83dee7d4 | ||
|
|
182c228eb8 | ||
|
|
fcad602e55 | ||
|
|
b703e986e0 | ||
|
|
02b366fd14 | ||
|
|
64f26486d2 | ||
|
|
2826702913 | ||
|
|
56351c6ee4 | ||
|
|
861d8db6d0 | ||
|
|
22b572cb46 | ||
|
|
79e5b955a0 | ||
|
|
43240af3e1 | ||
|
|
7d0f3ddf4d | ||
|
|
ede60391fa | ||
|
|
a56d4ea6eb | ||
|
|
ff4d501fda | ||
|
|
46b303bfc6 | ||
|
|
c8b528d305 | ||
|
|
93693bdfb0 | ||
|
|
b62c0fa38f | ||
|
|
8a67cdb029 | ||
|
|
65073be373 | ||
|
|
b9c47fd5d2 | ||
|
|
740c4f65ae | ||
|
|
f554719c13 | ||
|
|
5141ef626c | ||
|
|
647019932e | ||
|
|
b5266d3a58 | ||
|
|
e904599e39 | ||
|
|
04138d19fd | ||
|
|
3d15d29767 | ||
|
|
3cbd9841ef | ||
|
|
3abad1a1d2 | ||
|
|
fb731f0a55 | ||
|
|
99cbd4b471 | ||
|
|
a09ad29671 | ||
|
|
10db2436bf | ||
|
|
03e0c619f5 | ||
|
|
9744d6c7f3 | ||
|
|
3d40773770 | ||
|
|
707b1c5611 | ||
|
|
3e8aee2bb3 | ||
|
|
a78fe80086 | ||
|
|
79834f00b7 | ||
|
|
b4ebeaf5e8 | ||
|
|
b01674b18e | ||
|
|
c2f21cf47e | ||
|
|
ec313c3e17 | ||
|
|
eec9cae7a7 | ||
|
|
0d50244447 | ||
|
|
172d35e147 | ||
|
|
f008ee0c6c | ||
|
|
95d7f23517 | ||
|
|
fa4ca60d15 | ||
|
|
456b1fcfc3 | ||
|
|
91103187e5 | ||
|
|
c09cf4d88f | ||
|
|
cbe68e746f | ||
|
|
caf914e15b | ||
|
|
4ac82a6c17 | ||
|
|
5dc61e88f9 | ||
|
|
d307609289 | ||
|
|
baca381c82 | ||
|
|
066ec410ca | ||
|
|
14581ff166 | ||
|
|
bb8da26237 | ||
|
|
14b2b81957 | ||
|
|
7358258b6f | ||
|
|
83cfc181e0 | ||
|
|
70241b47aa | ||
|
|
f71c7390cd | ||
|
|
9deaa92b50 | ||
|
|
11c66a9272 | ||
|
|
36a70f759d | ||
|
|
f9b5f25001 | ||
|
|
07993ed526 | ||
|
|
82d7a06913 | ||
|
|
6db2a4bdf9 | ||
|
|
5de3d96fc7 | ||
|
|
2e6b2ba520 | ||
|
|
ba053703f5 | ||
|
|
c3c9b8eab1 | ||
|
|
cffb4bc7bf | ||
|
|
994be72697 | ||
|
|
2b4cb2229c | ||
|
|
0fea721f14 | ||
|
|
6d48c5f3d1 | ||
|
|
278a356dfc | ||
|
|
574fa7f240 | ||
|
|
d297154cd5 | ||
|
|
a375734dda | ||
|
|
199f5a90d6 | ||
|
|
a97d550e8c | ||
|
|
7685f76239 | ||
|
|
896615675a | ||
|
|
7bd76f541d | ||
|
|
9581d74330 | ||
|
|
0977d58228 | ||
|
|
2227eaa0f6 | ||
|
|
ea2dc4001e |
132
.circleci/config.yml
Normal file
132
.circleci/config.yml
Normal file
@ -0,0 +1,132 @@
|
||||
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
5
.gitignore
vendored
@ -299,3 +299,8 @@ BtcTransmuter/mydb.db
|
||||
BtcTransmuter\.Extension\.BtcPayServer/obj/Debug/netcoreapp2\.2/
|
||||
|
||||
BtcTransmuter\.Extension\.BtcPayServer/obj/
|
||||
|
||||
BtcTransmuter/Extensions/
|
||||
/BtcTransmuter/Extensions/
|
||||
/BtcTransmuter/mydb.db_old
|
||||
dest
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
<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>
|
||||
@ -0,0 +1,16 @@
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
BTCTransmuter.Extension.Tor/Models/TorService.cs
Normal file
9
BTCTransmuter.Extension.Tor/Models/TorService.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace BTCTransmuter.Extension.Tor.Models
|
||||
{
|
||||
public class TorService
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string OnionHost { get; set; }
|
||||
public int VirtualPort { get; set; }
|
||||
}
|
||||
}
|
||||
91
BTCTransmuter.Extension.Tor/Services/TorServices.cs
Normal file
91
BTCTransmuter.Extension.Tor/Services/TorServices.cs
Normal file
@ -0,0 +1,91 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
98
BTCTransmuter.Extension.Tor/Services/Torrc.cs
Normal file
98
BTCTransmuter.Extension.Tor/Services/Torrc.cs
Normal file
@ -0,0 +1,98 @@
|
||||
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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
22
BTCTransmuter.Extension.Tor/TorBtcTransmuterExtension.cs
Normal file
22
BTCTransmuter.Extension.Tor/TorBtcTransmuterExtension.cs
Normal file
@ -0,0 +1,22 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
@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>
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<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>
|
||||
@ -1,8 +1,30 @@
|
||||
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; }
|
||||
// }
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
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;
|
||||
@ -8,21 +11,31 @@ 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
|
||||
where TViewModel : TRecipeActionData, IActionViewModel
|
||||
{
|
||||
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)
|
||||
IRecipeManager recipeManager,
|
||||
IExternalServiceManager externalServiceManager)
|
||||
{
|
||||
_memoryCache = memoryCache;
|
||||
_userManager = userManager;
|
||||
_recipeManager = recipeManager;
|
||||
_externalServiceManager = externalServiceManager;
|
||||
}
|
||||
|
||||
[HttpGet("{identifier}")]
|
||||
@ -34,7 +47,11 @@ namespace BtcTransmuter.Abstractions.Actions
|
||||
return result.Error;
|
||||
}
|
||||
|
||||
return View(await BuildViewModel(result.Data));
|
||||
var vm = await BuildViewModel(result.Data);
|
||||
|
||||
vm.RecipeId = result.Data.RecipeId;
|
||||
vm.RecipeActionIdInGroupBeforeThisOne = RecipeActionIdInGroupBeforeThisOne;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpPost("{identifier}")]
|
||||
@ -50,8 +67,22 @@ 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);
|
||||
@ -63,6 +94,7 @@ namespace BtcTransmuter.Abstractions.Actions
|
||||
}
|
||||
|
||||
protected abstract Task<TViewModel> BuildViewModel(RecipeAction recipeAction);
|
||||
|
||||
protected abstract Task<(RecipeAction ToSave, TViewModel showViewModel)> BuildModel(
|
||||
TViewModel viewModel, RecipeAction mainModel);
|
||||
|
||||
@ -86,6 +118,31 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
@ -8,10 +10,11 @@ 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> : IActionHandler, IActionDescriptor
|
||||
public abstract class BaseActionHandler<TActionData, TActionResultData> : IActionHandler, IActionDescriptor
|
||||
{
|
||||
public abstract string ActionId { get; }
|
||||
public abstract string Name { get; }
|
||||
@ -19,6 +22,8 @@ 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)
|
||||
{
|
||||
@ -40,44 +45,29 @@ namespace BtcTransmuter.Abstractions.Actions
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task<bool> CanExecute(object triggerData, RecipeAction recipeAction);
|
||||
|
||||
public async Task<ActionHandlerResult> Execute(object triggerData, RecipeAction recipeAction)
|
||||
public virtual Task<bool> CanExecute(Dictionary<string, (object data, string json)> data, RecipeAction recipeAction)
|
||||
{
|
||||
if (await CanExecute(triggerData, recipeAction))
|
||||
{
|
||||
return await Execute(triggerData, recipeAction, recipeAction.Get<TActionData>());
|
||||
}
|
||||
|
||||
return new ActionHandlerResult()
|
||||
{
|
||||
Executed = false
|
||||
};
|
||||
return Task.FromResult(recipeAction.ActionId == ActionId);
|
||||
}
|
||||
|
||||
protected abstract Task<ActionHandlerResult> Execute(object triggerData, RecipeAction recipeAction,
|
||||
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,
|
||||
TActionData actionData);
|
||||
|
||||
/// <summary>
|
||||
/// https://dotnetfiddle.net/MoqJFk
|
||||
/// </summary>
|
||||
protected static string InterpolateString(string value, object @object)
|
||||
protected static string InterpolateString(string value, Dictionary<string, object> 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;
|
||||
}
|
||||
return InterpolationHelper.InterpolateString(value, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -11,6 +12,7 @@ namespace BtcTransmuter.Abstractions.Actions
|
||||
string Description { get; }
|
||||
|
||||
string ViewPartial { get; }
|
||||
Type ActionResultDataType { get; }
|
||||
Task<IActionResult> EditData(RecipeAction data);
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Data;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
@ -6,6 +7,9 @@ namespace BtcTransmuter.Abstractions.Actions
|
||||
{
|
||||
public interface IActionDispatcher
|
||||
{
|
||||
Task Dispatch(object triggerData, RecipeAction recipeAction);
|
||||
Task<IEnumerable<ActionHandlerResult>> Dispatch(Dictionary<string, (object data, string json)> triggerData,
|
||||
RecipeAction recipeAction);
|
||||
|
||||
Task Dispatch(Dictionary<string, (object data, string json)> data, RecipeActionGroup recipeActionGroup);
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Data;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
|
||||
namespace BtcTransmuter.Abstractions.Actions
|
||||
{
|
||||
public interface IActionHandler
|
||||
{
|
||||
Task<ActionHandlerResult> Execute(object triggerData, RecipeAction recipeAction);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
namespace BtcTransmuter.Abstractions.Actions
|
||||
{
|
||||
public class TypedActionHandlerResult<T>: ActionHandlerResult
|
||||
{
|
||||
public T TypedData
|
||||
{
|
||||
get => (T) Data;
|
||||
set => Data = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BtcTransmuter.Data\BtcTransmuter.Data.csproj" />
|
||||
|
||||
8
BtcTransmuter.Abstractions/Configuration/DatabaseType.cs
Normal file
8
BtcTransmuter.Abstractions/Configuration/DatabaseType.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace BtcTransmuter
|
||||
{
|
||||
public enum DatabaseType
|
||||
{
|
||||
Sqlite,
|
||||
Postgres
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -1,27 +1,31 @@
|
||||
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 : ExtensionBase, IConfigureAction, IConfigureServicesAction
|
||||
public abstract class BtcTransmuterExtension: IExtension
|
||||
{
|
||||
int IConfigureServicesAction.Priority => Priority;
|
||||
|
||||
int IConfigureAction.Priority => Priority;
|
||||
protected abstract int Priority { get; }
|
||||
public string HeaderPartial { get; }
|
||||
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; }
|
||||
|
||||
public IEnumerable<IActionDescriptor> Actions => GetInstancesOfTypeInOurAssembly<IActionDescriptor>();
|
||||
public IEnumerable<ITriggerDescriptor> Triggers => GetInstancesOfTypeInOurAssembly<ITriggerDescriptor>();
|
||||
@ -34,7 +38,12 @@ namespace BtcTransmuter.Abstractions.Extensions
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Execute(IServiceCollection serviceCollection, IServiceProvider serviceProvider)
|
||||
public void Execute(IServiceCollection serviceCollection, IServiceProvider serviceProvider)
|
||||
{
|
||||
Execute(serviceCollection);
|
||||
}
|
||||
|
||||
public virtual void Execute(IServiceCollection serviceCollection)
|
||||
{
|
||||
RegisterInstances(serviceCollection, new Type[]
|
||||
{
|
||||
@ -42,13 +51,17 @@ 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)
|
||||
private void RegisterInstances(IServiceCollection serviceCollection, Type[] validTypes, ServiceLifetime serviceLifetime = ServiceLifetime.Transient)
|
||||
{
|
||||
var types = serviceCollection
|
||||
.RegisterAssemblyPublicNonGenericClasses(Assembly.GetAssembly(GetType()))
|
||||
@ -61,14 +74,16 @@ namespace BtcTransmuter.Abstractions.Extensions
|
||||
foreach (var type in types.TypesToConsider)
|
||||
{
|
||||
Console.WriteLine($"Registering {type.FullName}");
|
||||
|
||||
}
|
||||
|
||||
types.AsPublicImplementedInterfaces();
|
||||
types.AsPublicImplementedInterfaces(serviceLifetime);
|
||||
}
|
||||
|
||||
private IEnumerable<T> GetInstancesOfTypeInOurAssembly<T>() where T : class
|
||||
{
|
||||
return ExtensionManager.GetInstances<T>(assembly => assembly == Assembly.GetAssembly(GetType()));
|
||||
return Assembly.GetAssembly(GetType()).GetTypes().Where(t => typeof(T).IsAssignableFrom(t) && !t.IsAbstract)
|
||||
.Select(type => (T) Activator.CreateInstance(type, new object[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
BtcTransmuter.Abstractions/Extensions/IExtension.cs
Normal file
10
BtcTransmuter.Abstractions/Extensions/IExtension.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace BtcTransmuter.Abstractions.Extensions
|
||||
{
|
||||
public interface IExtension
|
||||
{
|
||||
string Name { get; }
|
||||
string Version { get; }
|
||||
string Description { get; }
|
||||
string Authors { get; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
BtcTransmuter.Abstractions/Extensions/RequestExtensions.cs
Normal file
16
BtcTransmuter.Abstractions/Extensions/RequestExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
17
BtcTransmuter.Abstractions/Extensions/StringExtensions.cs
Normal file
17
BtcTransmuter.Abstractions/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,17 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,15 +19,15 @@ namespace BtcTransmuter.Abstractions.ExternalServices
|
||||
public abstract string Name { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string ViewPartial { get; }
|
||||
protected abstract string ControllerName { get; }
|
||||
public abstract string ControllerName { get; }
|
||||
|
||||
|
||||
public T GetData()
|
||||
public virtual T GetData()
|
||||
{
|
||||
return _data.Get<T>();
|
||||
}
|
||||
|
||||
public void SetData(T data)
|
||||
public virtual void SetData(T data)
|
||||
{
|
||||
_data.Set(data);
|
||||
}
|
||||
|
||||
@ -18,7 +18,8 @@ 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;
|
||||
@ -34,11 +35,12 @@ 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);
|
||||
|
||||
@ -67,7 +69,7 @@ namespace BtcTransmuter.Abstractions.ExternalServices
|
||||
statusMessage = "Data updated"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private async Task<(IActionResult Error, ExternalServiceData Data )> GetExternalServiceData(string identifier)
|
||||
{
|
||||
ExternalServiceData data = null;
|
||||
@ -80,6 +82,7 @@ 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
|
||||
@ -110,5 +113,11 @@ 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
78
BtcTransmuter.Abstractions/Helpers/InterpolationHelper.cs
Normal file
78
BtcTransmuter.Abstractions/Helpers/InterpolationHelper.cs
Normal file
@ -0,0 +1,78 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BtcTransmuter.Abstractions.Helpers
|
||||
@ -10,14 +11,14 @@ namespace BtcTransmuter.Abstractions.Helpers
|
||||
public static ICollection<ValidationResult> Validate<T>(string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsedData = JObject.Parse(data).ToObject<T>();
|
||||
{
|
||||
var parsedData = JsonConvert.DeserializeObject<T>(data);
|
||||
var vc = new ValidationContext(parsedData);
|
||||
var result = new List<ValidationResult>();
|
||||
Validator.TryValidateObject(data, vc, result);
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
return new List<ValidationResult>()
|
||||
{
|
||||
@ -25,8 +26,5 @@ namespace BtcTransmuter.Abstractions.Helpers
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
8
BtcTransmuter.Abstractions/Models/OrderBy.cs
Normal file
8
BtcTransmuter.Abstractions/Models/OrderBy.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace BtcTransmuter.Abstractions.Models
|
||||
{
|
||||
public class OrderBy<T>
|
||||
{
|
||||
public T Field { get; set; }
|
||||
public OrderDirection Direction { get; set; }
|
||||
}
|
||||
}
|
||||
8
BtcTransmuter.Abstractions/Models/OrderDirection.cs
Normal file
8
BtcTransmuter.Abstractions/Models/OrderDirection.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace BtcTransmuter.Abstractions.Models
|
||||
{
|
||||
public enum OrderDirection
|
||||
{
|
||||
Ascending,
|
||||
Descending
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Data;
|
||||
@ -9,15 +9,22 @@ 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);
|
||||
|
||||
}
|
||||
}
|
||||
18
BtcTransmuter.Abstractions/Recipes/RecipeInvocationsQuery.cs
Normal file
18
BtcTransmuter.Abstractions/Recipes/RecipeInvocationsQuery.cs
Normal file
@ -0,0 +1,18 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,10 @@ namespace BtcTransmuter.Abstractions.Recipes
|
||||
{
|
||||
public bool? Enabled { get; set; }
|
||||
|
||||
public string RecipeTriggerId { get; set; }
|
||||
public string TriggerId { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string RecipeId { get; set; }
|
||||
|
||||
public bool IncludeRecipeInvocations { get; set; }
|
||||
}
|
||||
}
|
||||
10
BtcTransmuter.Abstractions/Settings/ISettingsManager.cs
Normal file
10
BtcTransmuter.Abstractions/Settings/ISettingsManager.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BtcTransmuter.Abstractions.Settings
|
||||
{
|
||||
public interface ISettingsManager
|
||||
{
|
||||
Task<T> GetSettings<T>(string key);
|
||||
Task SaveSettings<T>(string key,T settings);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Abstractions.ExternalServices;
|
||||
using BtcTransmuter.Abstractions.Recipes;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -14,15 +15,18 @@ 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)
|
||||
IMemoryCache memoryCache,
|
||||
IExternalServiceManager externalServiceManager)
|
||||
{
|
||||
_recipeManager = recipeManager;
|
||||
_userManager = userManager;
|
||||
_externalServiceManager = externalServiceManager;
|
||||
_memoryCache = memoryCache;
|
||||
}
|
||||
|
||||
@ -57,7 +61,16 @@ 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
|
||||
{
|
||||
|
||||
@ -20,7 +20,7 @@ namespace BtcTransmuter.Abstractions.Triggers
|
||||
public abstract string Name { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string ViewPartial { get; }
|
||||
protected abstract string ControllerName { get; }
|
||||
public abstract string ControllerName { get; }
|
||||
|
||||
public Task<IActionResult> EditData(RecipeTrigger data)
|
||||
{
|
||||
@ -46,27 +46,32 @@ namespace BtcTransmuter.Abstractions.Triggers
|
||||
TTriggerData triggerData, TTriggerParameters parameters);
|
||||
|
||||
|
||||
public Task<bool> IsTriggered(ITrigger trigger, RecipeTrigger recipeTrigger)
|
||||
public async Task<bool> IsTriggered(ITrigger trigger, RecipeTrigger recipeTrigger)
|
||||
{
|
||||
if (recipeTrigger.TriggerId != trigger.Id || trigger.Id != TriggerId)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
var triggerData = trigger.Get<TTriggerData>();
|
||||
var triggerData = await GetTriggerData(trigger);
|
||||
|
||||
if (typeof(TTriggerParameters).IsAssignableFrom(typeof(IUseExternalService)) &&
|
||||
if (typeof(IUseExternalService).IsAssignableFrom(typeof(TTriggerData)) &&
|
||||
((IUseExternalService) triggerData).ExternalServiceId != recipeTrigger.ExternalServiceId)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
return IsTriggered(trigger, recipeTrigger, triggerData, recipeTrigger.Get<TTriggerParameters>());
|
||||
return await IsTriggered(trigger, recipeTrigger, triggerData, recipeTrigger.Get<TTriggerParameters>());
|
||||
}
|
||||
|
||||
public Task<object> GetData(ITrigger trigger)
|
||||
public async Task<object> GetData(ITrigger trigger)
|
||||
{
|
||||
return Task.FromResult((object) trigger.Get<TTriggerData>());
|
||||
return await GetTriggerData(trigger);
|
||||
}
|
||||
|
||||
public virtual Task<TTriggerData> GetTriggerData(ITrigger trigger)
|
||||
{
|
||||
return Task.FromResult(trigger.Get<TTriggerData>());
|
||||
}
|
||||
|
||||
public virtual Task AfterExecution(IEnumerable<Recipe> tupleItem1)
|
||||
|
||||
23
BtcTransmuter.Abstractions/Validations.cs
Normal file
23
BtcTransmuter.Abstractions/Validations.cs
Normal file
@ -0,0 +1,23 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
8
BtcTransmuter.Data/Encryption/EncryptedAttribute.cs
Normal file
8
BtcTransmuter.Data/Encryption/EncryptedAttribute.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace BtcTransmuter.Data.Encryption
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
|
||||
sealed class EncryptedAttribute : Attribute
|
||||
{ }
|
||||
}
|
||||
22
BtcTransmuter.Data/Encryption/EncryptedConverter.cs
Normal file
22
BtcTransmuter.Data/Encryption/EncryptedConverter.cs
Normal file
@ -0,0 +1,22 @@
|
||||
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)}";
|
||||
}
|
||||
}
|
||||
25
BtcTransmuter.Data/Encryption/ModelBuilderExtensions.cs
Normal file
25
BtcTransmuter.Data/Encryption/ModelBuilderExtensions.cs
Normal file
@ -0,0 +1,25 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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: IHasJsonData
|
||||
public abstract class BaseEntity :BaseIdEntity, IHasJsonData
|
||||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
public string DataJson { get; set; }
|
||||
[Encrypted] public string DataJson { get; set; }
|
||||
}
|
||||
}
|
||||
10
BtcTransmuter.Data/Entities/BaseIdEntity.cs
Normal file
10
BtcTransmuter.Data/Entities/BaseIdEntity.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace BtcTransmuter.Data.Entities
|
||||
{
|
||||
public abstract class BaseIdEntity
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,8 @@ using System.Collections.Generic;
|
||||
|
||||
namespace BtcTransmuter.Data.Entities
|
||||
{
|
||||
public class Recipe
|
||||
public class Recipe : BaseIdEntity
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string UserId { get; set; }
|
||||
public string Name { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
@ -12,6 +11,14 @@ 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
|
||||
}
|
||||
}
|
||||
@ -5,11 +5,19 @@ 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 List<RecipeInvocation> RecipeInvocations { 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}")}";
|
||||
}
|
||||
}
|
||||
}
|
||||
13
BtcTransmuter.Data/Entities/RecipeActionGroup.cs
Normal file
13
BtcTransmuter.Data/Entities/RecipeActionGroup.cs
Normal file
@ -0,0 +1,13 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,17 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BtcTransmuter.Data.Entities
|
||||
{
|
||||
public class RecipeInvocation
|
||||
public class RecipeInvocation: BaseIdEntity
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string RecipeId { get; set; }
|
||||
public string RecipeActionId { get; set; }
|
||||
|
||||
public string RecipeAction { 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; }
|
||||
}
|
||||
}
|
||||
7
BtcTransmuter.Data/Entities/Settings.cs
Normal file
7
BtcTransmuter.Data/Entities/Settings.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BtcTransmuter.Data.Entities
|
||||
{
|
||||
public class Settings: BaseEntity
|
||||
{
|
||||
public string Key { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using BtcTransmuter.Data.Encryption;
|
||||
using BtcTransmuter.Data.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace BtcTransmuter.Data.Entities
|
||||
{
|
||||
public class User : IdentityUser
|
||||
public class User : IdentityUser, IHasJsonData
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BtcTransmuter.Data.Models
|
||||
@ -6,12 +7,12 @@ namespace BtcTransmuter.Data.Models
|
||||
{
|
||||
public static T Get<T>(this IHasJsonData i)
|
||||
{
|
||||
return JObject.Parse(i.DataJson ?? "{}").ToObject<T>();
|
||||
return JsonConvert.DeserializeObject<T>(i.DataJson ?? "{}");
|
||||
}
|
||||
|
||||
public static void Set<T>(this IHasJsonData i, T data)
|
||||
{
|
||||
i.DataJson = JObject.FromObject(data).ToString();
|
||||
i.DataJson = JsonConvert.SerializeObject(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
<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>
|
||||
@ -0,0 +1,15 @@
|
||||
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>
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
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>>
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,306 @@
|
||||
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)
|
||||
}));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using BtcTransmuter.Abstractions.Actions;
|
||||
|
||||
namespace BtcTransmuter.Extension.BtcPayServer.Actions
|
||||
{
|
||||
public class BtcPayServerActionHandlerResult<T> : TypedActionHandlerResult<T>
|
||||
{
|
||||
public override string DataJson => NBitcoin.JsonConverters.Serializer.ToString(Data);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BtcTransmuter.Extension.BtcPayServer.Actions.GetInvoice
|
||||
{
|
||||
public class GetInvoiceData
|
||||
{
|
||||
[Required]
|
||||
public string InvoiceId { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
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}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
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}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,10 @@
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,26 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\BtcTransmuter.Abstractions\BtcTransmuter.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\BtcTransmuter.Extension.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Views\BtcPayServer\EditData.cshtml" />
|
||||
<Content Include="Views\InvoiceStatusChanged\EditData.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Styles\**;Views\**;Scripts\**" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@ -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(),
|
||||
PairingUrl = await client.GetPairingUrl(data.Name),
|
||||
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(),
|
||||
PairingUrl = await service.GetPairingUrl(mainModel.Name),
|
||||
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();
|
||||
viewModel.PairingUrl = await service.GetPairingUrl(mainModel.Name);
|
||||
viewModel.Paired = false;
|
||||
if (!string.IsNullOrEmpty(viewModel.PairingCode))
|
||||
{
|
||||
|
||||
@ -6,7 +6,8 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
|
||||
{
|
||||
public class BtcPayServerExternalServiceData
|
||||
{
|
||||
[Required] public Uri Server { get; set; }
|
||||
[Required][Display(Name = "BtcPay Host Url")]
|
||||
[Validation.Uri] public Uri Server { get; set; }
|
||||
|
||||
public string Seed { get; set; }
|
||||
|
||||
|
||||
@ -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";
|
||||
protected override string ControllerName => "BtcPayServer";
|
||||
public override string ControllerName => "BtcPayServer";
|
||||
|
||||
|
||||
public BtcPayServerService() : base()
|
||||
@ -30,7 +30,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
|
||||
{
|
||||
}
|
||||
|
||||
public Bitpay ConstructClient()
|
||||
public virtual Bitpay ConstructClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -45,13 +45,20 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> CheckAccess()
|
||||
public virtual async Task<bool> CheckAccess()
|
||||
{
|
||||
var client = ConstructClient();
|
||||
return client != null && await client.TestAccessAsync(Facade.Merchant);
|
||||
try
|
||||
{
|
||||
var client = ConstructClient();
|
||||
return client != null && await client.TestAccessAsync(Facade.Merchant);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> GetPairingUrl()
|
||||
public async Task<string> GetPairingUrl(string label)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -62,14 +69,14 @@ namespace BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer
|
||||
return null;
|
||||
}
|
||||
|
||||
return (await client.RequestClientAuthorizationAsync("BtcTransmuter", Facade.Merchant))
|
||||
return (await client.RequestClientAuthorizationAsync(label, Facade.Merchant))
|
||||
.CreateLink(client.BaseUrl)
|
||||
.ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var data = GetData();
|
||||
return new Uri(data.Server, "api-tokens").ToString();
|
||||
return Uri.TryCreate(data.Server, "api-tokens", out var result) ? result.ToString() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,17 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -68,5 +68,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ 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)
|
||||
@ -39,19 +40,24 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
|
||||
_externalServices = new ConcurrentDictionary<string, BtcPayServerService>(
|
||||
externalServices
|
||||
.Select(service =>
|
||||
new KeyValuePair<string, BtcPayServerService>(service.Id, new BtcPayServerService(service))));
|
||||
new KeyValuePair<string, BtcPayServerService>(service.Id, GetServiceFromData(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(TimeSpan.FromMinutes(1), cancellationToken);
|
||||
await Task.Delay(CheckInterval, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,33 +65,46 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
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)
|
||||
var key = pair.Key;
|
||||
var service = pair.Value;
|
||||
if (!await service.CheckAccess())
|
||||
{
|
||||
if (data.MonitoredInvoiceStatuses.ContainsKey(invoice.Id))
|
||||
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[invoice.Id] != invoice.Status)
|
||||
if (data.MonitoredInvoiceStatuses.ContainsKey(invoice.Id))
|
||||
{
|
||||
await _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
|
||||
if (data.MonitoredInvoiceStatuses[invoice.Id] != invoice.Status)
|
||||
{
|
||||
_ = _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
|
||||
{
|
||||
Data = new InvoiceStatusChangedTriggerData()
|
||||
{
|
||||
Invoice = invoice,
|
||||
ExternalServiceId = key
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
|
||||
{
|
||||
Data = new InvoiceStatusChangedTriggerData()
|
||||
{
|
||||
@ -95,26 +114,13 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _triggerDispatcher.DispatchTrigger(new InvoiceStatusChangedTrigger()
|
||||
{
|
||||
Data = new InvoiceStatusChangedTriggerData()
|
||||
{
|
||||
Invoice = invoice,
|
||||
ExternalServiceId = key
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data.MonitoredInvoiceStatuses.AddOrReplace(invoice.Id, invoice.Status);
|
||||
}
|
||||
|
||||
data.MonitoredInvoiceStatuses.AddOrReplace(invoice.Id, invoice.Status);
|
||||
}
|
||||
service.SetData(data);
|
||||
|
||||
|
||||
service.SetData( data);
|
||||
await _externalServiceManager.UpdateInternalData(key, data);
|
||||
|
||||
await _externalServiceManager.UpdateInternalData(key, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -137,13 +143,13 @@ namespace BtcTransmuter.Extension.BtcPayServer.HostedServices
|
||||
switch (e.Action)
|
||||
{
|
||||
case UpdatedItem<ExternalServiceData>.UpdateAction.Added:
|
||||
_externalServices.TryAdd(e.Item.Id, new BtcPayServerService(e.Item));
|
||||
_externalServices.TryAdd(e.Item.Id, GetServiceFromData(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, new BtcPayServerService(e.Item), null);
|
||||
_externalServices.TryUpdate(e.Item.Id, GetServiceFromData(e.Item), null);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
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;
|
||||
@ -16,26 +20,42 @@ using NBitpayClient;
|
||||
namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
|
||||
{
|
||||
[Authorize]
|
||||
[Route("btcpayserver-plugin/triggers/invoice-status-changed")]
|
||||
[Route("btcpayserver-plugin/triggers/[controller]")]
|
||||
public class InvoiceStatusChangedController : BaseTriggerController<
|
||||
InvoiceStatusChangedController.InvoiceStatusChangedTriggerViewModel, InvoiceStatusChangedTriggerParameters>
|
||||
{
|
||||
private readonly IExternalServiceManager _externalServiceManager;
|
||||
|
||||
private readonly SelectListItem[] AllowedStatuses = new SelectListItem[]
|
||||
public static 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 = "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"},
|
||||
};
|
||||
|
||||
|
||||
public InvoiceStatusChangedController(IRecipeManager recipeManager, UserManager<User> userManager,
|
||||
public static SelectListItem[] GetAvailableStatuses(string[] usedKeys)
|
||||
{
|
||||
return AllowedStatuses.Where(item => !usedKeys.Contains(item.Value)).ToArray();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public InvoiceStatusChangedController(IRecipeManager recipeManager, UserManager<User> userManager,
|
||||
IMemoryCache memoryCache, IExternalServiceManager externalServiceManager) : base(recipeManager, userManager,
|
||||
memoryCache)
|
||||
memoryCache, externalServiceManager)
|
||||
{
|
||||
_externalServiceManager = externalServiceManager;
|
||||
}
|
||||
@ -56,8 +76,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
|
||||
nameof(ExternalServiceData.Name), data.ExternalServiceId),
|
||||
RecipeId = data.RecipeId,
|
||||
ExternalServiceId = data.ExternalServiceId,
|
||||
Status = fromData.Status,
|
||||
Statuses = new SelectList(AllowedStatuses, "Value", "Text", fromData.Status),
|
||||
Conditions = fromData.Conditions,
|
||||
};
|
||||
}
|
||||
|
||||
@ -65,7 +84,7 @@ namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
|
||||
BuildModel(
|
||||
InvoiceStatusChangedTriggerViewModel viewModel, RecipeTrigger mainModel)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
if (viewModel.Action != "EditData" || !ModelState.IsValid)
|
||||
{
|
||||
var btcPayServices = await _externalServiceManager.GetExternalServicesData(
|
||||
new ExternalServicesDataQuery()
|
||||
@ -73,12 +92,29 @@ 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);
|
||||
return (null, viewModel);
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
mainModel.ExternalServiceId = viewModel.ExternalServiceId;
|
||||
@ -88,10 +124,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] public string ExternalServiceId { get; set; }
|
||||
public SelectList Statuses { get; set; }
|
||||
[Required][Display(Name = "BtcPay Service")] public string ExternalServiceId { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,13 @@ 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";
|
||||
@ -15,19 +17,30 @@ 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";
|
||||
protected override string ControllerName => "InvoiceStatusChanged";
|
||||
public override string ControllerName => "InvoiceStatusChanged";
|
||||
|
||||
protected override Task<bool> IsTriggered(ITrigger trigger, RecipeTrigger recipeTrigger,
|
||||
InvoiceStatusChangedTriggerData triggerData,
|
||||
InvoiceStatusChangedTriggerParameters parameters)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parameters.Status))
|
||||
|
||||
var exceptionStatus =Invoice.EXSTATUS_FALSE;
|
||||
if (triggerData.Invoice.ExceptionStatus.Type == JTokenType.String)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
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(triggerData.Invoice.Status.Equals(parameters.Status,
|
||||
StringComparison.InvariantCultureIgnoreCase));
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,22 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BtcTransmuter.Abstractions;
|
||||
|
||||
namespace BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged
|
||||
{
|
||||
public class InvoiceStatusChangedTriggerParameters
|
||||
{
|
||||
public string Status { get; set; }
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,13 +9,14 @@
|
||||
|
||||
<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"/>
|
||||
@ -24,12 +25,17 @@
|
||||
|
||||
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"/>
|
||||
@ -39,7 +45,10 @@
|
||||
{
|
||||
<button type="submit" value="unpair" name="action" class="btn btn-danger">Unpair</button>
|
||||
}
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
else
|
||||
{
|
||||
<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>
|
||||
@ -0,0 +1,36 @@
|
||||
@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
|
||||
})
|
||||
@ -0,0 +1,46 @@
|
||||
@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
|
||||
})
|
||||
@ -1,3 +1,4 @@
|
||||
@using Microsoft.EntityFrameworkCore.Internal
|
||||
@model BtcTransmuter.Extension.BtcPayServer.Triggers.InvoiceStatusChanged.InvoiceStatusChangedController.InvoiceStatusChangedTriggerViewModel
|
||||
|
||||
|
||||
@ -14,15 +15,52 @@
|
||||
<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="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 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>
|
||||
<input type="hidden" asp-for="RecipeId" />
|
||||
<div class="mt-2">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<button type="submit" name="" value="" 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>
|
||||
@ -0,0 +1,9 @@
|
||||
@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>
|
||||
@ -0,0 +1,9 @@
|
||||
@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>
|
||||
@ -6,10 +6,15 @@
|
||||
}
|
||||
|
||||
<div>
|
||||
Btcpayserver Invoice Status Change
|
||||
@if (!string.IsNullOrEmpty(data.Status))
|
||||
{
|
||||
<span>to @data.Status</span>
|
||||
}
|
||||
|
||||
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>
|
||||
</div>
|
||||
@ -0,0 +1 @@
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@ -0,0 +1,24 @@
|
||||
<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.DynamicServices\BtcTransmuter.Extension.DynamicServices.csproj" />
|
||||
<ProjectReference Include="..\BtcTransmuter.Tests.Base\BtcTransmuter.Tests.Base.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@ -0,0 +1,15 @@
|
||||
using BtcTransmuter.Extension.Webhook.Triggers.DynamicServiceMarker;
|
||||
using BtcTransmuter.Tests.Base;
|
||||
|
||||
namespace BtcTransmuter.Extension.DynamicServices.Tests
|
||||
{
|
||||
public class DynamicServiceMarkerTriggerHandlerTests : BaseTriggerTest<DynamicServiceMarkerTriggerHandler,
|
||||
DynamicServiceMarkerTriggerData,
|
||||
DynamicServiceMarkerTriggerParameters>
|
||||
{
|
||||
protected override DynamicServiceMarkerTriggerHandler GetTriggerHandlerInstance(params object[] setupArgs)
|
||||
{
|
||||
return new DynamicServiceMarkerTriggerHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Abstractions.Actions;
|
||||
using BtcTransmuter.Abstractions.ExternalServices;
|
||||
using BtcTransmuter.Abstractions.Helpers;
|
||||
using BtcTransmuter.Abstractions.Recipes;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
using BtcTransmuter.Extension.BtcPayServer.ExternalServices.BtcPayServer;
|
||||
using BtcTransmuter.Extension.DynamicService.ExternalServices.DynamicService;
|
||||
using BtcTransmuter.Extension.Lightning.ExternalServices.NBXplorerWallet;
|
||||
using BtcTransmuter.Extension.NBXplorer.Services;
|
||||
using BtcTransmuter.Tests.Base;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace BtcTransmuter.Extension.DynamicServices.Tests
|
||||
{
|
||||
public class
|
||||
DynamicServiceServiceTests : BaseExternalServiceTest<DynamicServiceService, DynamicServiceExternalServiceData>
|
||||
{
|
||||
public override Task CanSerializeData()
|
||||
{
|
||||
ConfigureDependencyHelper();
|
||||
using (var scope = DependencyHelper.ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var x = GetExternalService();
|
||||
var instance = new DynamicServiceExternalServiceData();
|
||||
var externalService = GetExternalService(new ExternalServiceData()
|
||||
{
|
||||
Type = x.ExternalServiceType
|
||||
},
|
||||
scope.ServiceProvider.GetRequiredService<IRecipeManager>(),
|
||||
scope.ServiceProvider.GetRequiredService<IActionDispatcher>(),
|
||||
scope.ServiceProvider.GetRequiredService<IExternalServiceManager>()
|
||||
|
||||
);
|
||||
externalService.SetData(instance);
|
||||
externalService.GetData();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override DynamicServiceService GetExternalService(params object[] setupArgs)
|
||||
{
|
||||
if (setupArgs?.Any()?? false)
|
||||
{
|
||||
|
||||
return new DynamicServiceService(
|
||||
(ExternalServiceData) setupArgs.First(o => o is ExternalServiceData),
|
||||
(IRecipeManager) setupArgs.First(o => o is IRecipeManager),
|
||||
(IActionDispatcher) setupArgs.First(o => o is IActionDispatcher),
|
||||
(IExternalServiceManager) setupArgs.First(o => o is IExternalServiceManager));
|
||||
}
|
||||
return new DynamicServiceService();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<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>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@ -0,0 +1,9 @@
|
||||
using BtcTransmuter.Abstractions.Extensions;
|
||||
|
||||
namespace BtcTransmuter.Extension.DynamicServices
|
||||
{
|
||||
public class DynamicServicesBtcTransmuterExtension : BtcTransmuterExtension
|
||||
{
|
||||
public override string Name => "Dynamic Services Plugin";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Abstractions.ExternalServices;
|
||||
using BtcTransmuter.Abstractions.Recipes;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
using BtcTransmuter.Data.Models;
|
||||
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.DynamicService.ExternalServices.DynamicService
|
||||
{
|
||||
[Route("DynamicService-plugin/external-services/DynamicService")]
|
||||
[Authorize]
|
||||
public class DynamicServiceController : BaseExternalServiceController<EditDynamicServiceDataViewModel>
|
||||
{
|
||||
private readonly UserManager<User> _userManager;
|
||||
private readonly IRecipeManager _recipeManager;
|
||||
|
||||
public DynamicServiceController(IExternalServiceManager externalServiceManager, UserManager<User> userManager,
|
||||
IMemoryCache memoryCache, IRecipeManager recipeManager) : base(externalServiceManager, userManager, memoryCache)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_recipeManager = recipeManager;
|
||||
}
|
||||
|
||||
protected override string ExternalServiceType => DynamicServiceService.DynamicServiceServiceType;
|
||||
|
||||
protected override async Task<EditDynamicServiceDataViewModel> BuildViewModel(ExternalServiceData data)
|
||||
{
|
||||
var client = new DynamicServiceService(data, null, null, null);
|
||||
var clientData = client.GetData();
|
||||
|
||||
var recipes = await _recipeManager.GetRecipes(new RecipesQuery()
|
||||
{
|
||||
UserId = _userManager.GetUserId(User)
|
||||
});
|
||||
var recipe = string.IsNullOrEmpty(clientData.RecipeId)
|
||||
? null
|
||||
: recipes.SingleOrDefault(recipe1 =>
|
||||
recipe1.Id.Equals(clientData.RecipeId, StringComparison.InvariantCultureIgnoreCase));
|
||||
return new EditDynamicServiceDataViewModel()
|
||||
{
|
||||
Value = clientData.Value,
|
||||
RecipeId = clientData.RecipeId,
|
||||
RecipeActionId = clientData.RecipeActionId,
|
||||
RecipeActionGroupId = clientData.RecipeActionGroupId,
|
||||
Recipes = new SelectList(recipes, nameof(Recipe.Id), nameof(Recipe.Name), clientData.RecipeId),
|
||||
RecipeActions =recipe == null? null : new SelectList(recipe.RecipeActions, nameof(RecipeAction.Id), nameof(RecipeAction.ActionId), clientData.RecipeActionId),
|
||||
RecipeActionGroups = recipe == null? null : new SelectList(recipe.RecipeActionGroups, nameof(RecipeActionGroup.Id), nameof(RecipeActionGroup.Id), clientData.RecipeActionGroupId),
|
||||
};
|
||||
}
|
||||
|
||||
protected override async Task<(ExternalServiceData ToSave, EditDynamicServiceDataViewModel showViewModel)>
|
||||
BuildModel(EditDynamicServiceDataViewModel viewModel, ExternalServiceData mainModel)
|
||||
{
|
||||
if (string.IsNullOrEmpty(viewModel.RecipeActionId) && string.IsNullOrEmpty(viewModel.RecipeActionGroupId))
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "please select a recipe action or a recipe action group ");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var recipes = await _recipeManager.GetRecipes(new RecipesQuery()
|
||||
{
|
||||
UserId = _userManager.GetUserId(User)
|
||||
});
|
||||
var recipe = string.IsNullOrEmpty(viewModel.RecipeId)
|
||||
? null
|
||||
: recipes.SingleOrDefault(recipe1 =>
|
||||
recipe1.Id.Equals(viewModel.RecipeId, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
|
||||
|
||||
return (null, new EditDynamicServiceDataViewModel()
|
||||
{
|
||||
Value = viewModel.Value,
|
||||
RecipeId = viewModel.RecipeId,
|
||||
RecipeActionId = viewModel.RecipeActionId,
|
||||
RecipeActionGroupId = viewModel.RecipeActionGroupId,
|
||||
Recipes = new SelectList(recipes, nameof(Recipe.Id), nameof(Recipe.Name), viewModel.RecipeId),
|
||||
RecipeActions =recipe == null? null : new SelectList(recipe.RecipeActions, nameof(RecipeAction.Id), nameof(RecipeAction.ActionId), viewModel.RecipeActionId),
|
||||
RecipeActionGroups = recipe == null? null : new SelectList(recipe.RecipeActionGroups, nameof(RecipeActionGroup.Id), nameof(RecipeActionGroup.Id), viewModel.RecipeActionGroupId),
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
mainModel.Set(viewModel);
|
||||
return (mainModel, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace BtcTransmuter.Extension.DynamicService.ExternalServices.DynamicService
|
||||
{
|
||||
public class DynamicServiceExternalServiceData
|
||||
{
|
||||
public string RecipeId { get; set; }
|
||||
public string RecipeActionId { get; set; }
|
||||
public string RecipeActionGroupId { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Abstractions.Actions;
|
||||
using BtcTransmuter.Abstractions.ExternalServices;
|
||||
using BtcTransmuter.Abstractions.Helpers;
|
||||
using BtcTransmuter.Abstractions.Recipes;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
namespace BtcTransmuter.Extension.DynamicService.ExternalServices.DynamicService
|
||||
{
|
||||
public class DynamicServiceService : BaseExternalService<DynamicServiceExternalServiceData>
|
||||
{
|
||||
private readonly ExternalServiceData _data;
|
||||
private readonly IRecipeManager _recipeManager;
|
||||
private readonly IActionDispatcher _actionDispatcher;
|
||||
private readonly IExternalServiceManager _externalServiceManager;
|
||||
public const string DynamicServiceServiceType = "DynamicServiceExternalService";
|
||||
public override string ExternalServiceType => DynamicServiceServiceType;
|
||||
|
||||
public override string Name => "DynamicService External Service";
|
||||
|
||||
public override string Description =>
|
||||
"DynamicService External Service to be able to resolve other external services using actions";
|
||||
|
||||
public override string ViewPartial => "ViewDynamicServiceExternalService";
|
||||
public override string ControllerName => "DynamicService";
|
||||
|
||||
|
||||
public DynamicServiceService() : base()
|
||||
{
|
||||
}
|
||||
|
||||
public DynamicServiceService(ExternalServiceData data, IRecipeManager recipeManager,
|
||||
IActionDispatcher actionDispatcher, IExternalServiceManager externalServiceManager) : base(data)
|
||||
{
|
||||
_data = data;
|
||||
_recipeManager = recipeManager;
|
||||
_actionDispatcher = actionDispatcher;
|
||||
_externalServiceManager = externalServiceManager;
|
||||
}
|
||||
|
||||
public async Task<ExternalServiceData> ExecuteWitchcraftToComputeExternalService()
|
||||
{
|
||||
var data = GetData();
|
||||
var recipe = await _recipeManager.GetRecipe(data.RecipeId);
|
||||
if (recipe == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var actionData = new Dictionary<string, (object data, string json)>();
|
||||
if (!string.IsNullOrEmpty(data.RecipeActionId))
|
||||
{
|
||||
var recipeAction = recipe.RecipeActions.SingleOrDefault(action =>
|
||||
action.Id.Equals(data.RecipeActionId, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (recipeAction == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var dispatchResult = await _actionDispatcher.Dispatch(actionData,
|
||||
recipeAction);
|
||||
actionData.Add("PreviousAction", (dispatchResult.First().Data, dispatchResult.First().DataJson));
|
||||
actionData.Add("ActionData0", (dispatchResult.First().Data, dispatchResult.First().DataJson));
|
||||
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(data.RecipeActionGroupId))
|
||||
{
|
||||
var recipeActionGroup = recipe.RecipeActionGroups.SingleOrDefault(action =>
|
||||
action.Id.Equals(data.RecipeActionGroupId, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (recipeActionGroup == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await _actionDispatcher.Dispatch(actionData,
|
||||
recipeActionGroup);
|
||||
}
|
||||
|
||||
var value = InterpolationHelper.InterpolateString(data.Value,
|
||||
actionData.ToDictionary(pair => pair.Key, pair => pair.Value.data));
|
||||
|
||||
return await _externalServiceManager.GetExternalServiceData(value, _data.UserId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace BtcTransmuter.Extension.DynamicService.ExternalServices.DynamicService
|
||||
{
|
||||
public class EditDynamicServiceDataViewModel : DynamicServiceExternalServiceData
|
||||
{
|
||||
public string Action { get; set; }
|
||||
public SelectList Recipes { get; set; }
|
||||
public SelectList RecipeActions { get; set; }
|
||||
public SelectList RecipeActionGroups { get; set; }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Abstractions.Actions;
|
||||
using BtcTransmuter.Abstractions.ExternalServices;
|
||||
using BtcTransmuter.Abstractions.Helpers;
|
||||
using BtcTransmuter.Abstractions.Recipes;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
using BtcTransmuter.Extension.DynamicService.ExternalServices.DynamicService;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace BtcTransmuter.Extension.DynamicServices
|
||||
{
|
||||
public static class RecipeActionExtensions
|
||||
{
|
||||
public static async Task<ExternalServiceData> GetExternalService(this RecipeAction recipeAction)
|
||||
{
|
||||
if (recipeAction.ExternalService == null || !recipeAction.ExternalService.Type.Equals(
|
||||
DynamicServiceService.DynamicServiceServiceType, StringComparison.InvariantCultureIgnoreCase))
|
||||
return recipeAction.ExternalService;
|
||||
|
||||
|
||||
using (var scope = DependencyHelper.ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var service = new DynamicServiceService(recipeAction.ExternalService,
|
||||
scope.ServiceProvider.GetService<IRecipeManager>(),
|
||||
scope.ServiceProvider.GetService<IActionDispatcher>(),
|
||||
scope.ServiceProvider.GetService<IExternalServiceManager>());
|
||||
|
||||
return await service.ExecuteWitchcraftToComputeExternalService();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
using System.Threading.Tasks;
|
||||
using BtcTransmuter.Abstractions.ExternalServices;
|
||||
using BtcTransmuter.Abstractions.Recipes;
|
||||
using BtcTransmuter.Abstractions.Triggers;
|
||||
using BtcTransmuter.Data.Entities;
|
||||
using BtcTransmuter.Data.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BtcTransmuter.Extension.Webhook.Triggers.DynamicServiceMarker
|
||||
{
|
||||
[Authorize]
|
||||
[Route("DynamicService-plugin/triggers/[controller]")]
|
||||
public class DynamicServiceMarkerController : BaseTriggerController<DynamicServiceMarkerTriggerParametersViewModel, DynamicServiceMarkerTriggerParameters>
|
||||
{
|
||||
|
||||
public DynamicServiceMarkerController(IRecipeManager recipeManager, UserManager<User> userManager,
|
||||
IMemoryCache memoryCache,
|
||||
IExternalServiceManager externalServiceManager) : base(recipeManager, userManager, memoryCache,
|
||||
externalServiceManager)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Task<DynamicServiceMarkerTriggerParametersViewModel> BuildViewModel(RecipeTrigger data)
|
||||
{
|
||||
return Task.FromResult(new DynamicServiceMarkerTriggerParametersViewModel()
|
||||
{
|
||||
RecipeId = data.RecipeId
|
||||
});
|
||||
}
|
||||
|
||||
protected override Task<(RecipeTrigger ToSave, DynamicServiceMarkerTriggerParametersViewModel showViewModel)> BuildModel(
|
||||
DynamicServiceMarkerTriggerParametersViewModel viewModel, RecipeTrigger mainModel)
|
||||
{
|
||||
mainModel.Set(viewModel);
|
||||
|
||||
return Task.FromResult<(RecipeTrigger ToSave, DynamicServiceMarkerTriggerParametersViewModel showViewModel)>((mainModel,
|
||||
null));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
using BtcTransmuter.Abstractions.Triggers;
|
||||
|
||||
namespace BtcTransmuter.Extension.Webhook.Triggers.DynamicServiceMarker
|
||||
{
|
||||
public class DynamicServiceMarkerTrigger : BaseTrigger<DynamicServiceMarkerTriggerData>
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
using System.Net.Http;
|
||||
|
||||
namespace BtcTransmuter.Extension.Webhook.Triggers.DynamicServiceMarker
|
||||
{
|
||||
public class DynamicServiceMarkerTriggerData
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user