Compare commits
62 Commits
feat/webho
...
feat/email
Author | SHA1 | Date | |
---|---|---|---|
![]() |
85630a59c1 | ||
![]() |
b4ef196bfb | ||
![]() |
099b2a39b4 | ||
![]() |
2d07baedf4 | ||
![]() |
8b34e001ef | ||
![]() |
617dcdde53 | ||
![]() |
f2fb800323 | ||
![]() |
db4d711cba | ||
![]() |
9ba1239c11 | ||
![]() |
ed7ed73980 | ||
![]() |
0f081ac3c8 | ||
![]() |
3aa0fb20ce | ||
![]() |
891c885f20 | ||
![]() |
89606615dc | ||
![]() |
ecab47b2ea | ||
![]() |
882756ef3a | ||
![]() |
a208c87c29 | ||
![]() |
70ea463f60 | ||
![]() |
79c94fcaf0 | ||
![]() |
3b925bb072 | ||
![]() |
df17ea8f40 | ||
![]() |
94066d4408 | ||
![]() |
41468b5b60 | ||
![]() |
1c61fcc17a | ||
![]() |
a102924fd7 | ||
![]() |
390846c85f | ||
![]() |
a48b809a89 | ||
![]() |
cd46da60a0 | ||
![]() |
50f52a99b4 | ||
![]() |
150b1e5712 | ||
![]() |
1f7eee43e2 | ||
![]() |
7c441fff14 | ||
![]() |
647cc1d9bf | ||
![]() |
97b1d8d66f | ||
![]() |
2cce1c4e93 | ||
![]() |
8b1511a07b | ||
![]() |
a69dd95992 | ||
![]() |
d3260f4f32 | ||
![]() |
301bde4da2 | ||
![]() |
913c5c94fb | ||
![]() |
610896b6f5 | ||
![]() |
33f79872be | ||
![]() |
8fc52d76dc | ||
![]() |
aa12757155 | ||
![]() |
847c364ad1 | ||
![]() |
eabc943452 | ||
![]() |
41a0f15e16 | ||
![]() |
e2294c24d0 | ||
![]() |
a3c0a0422c | ||
![]() |
d837b1590a | ||
![]() |
283e570ebb | ||
![]() |
14c74f6566 | ||
![]() |
8e655daa71 | ||
![]() |
fed092bb65 | ||
![]() |
6d28290605 | ||
![]() |
2de0ea57d0 | ||
![]() |
f2886e6da8 | ||
![]() |
6b57bce6d9 | ||
![]() |
bfbeb6add2 | ||
![]() |
1fe0d65874 | ||
![]() |
bfaa0f9d89 | ||
![]() |
4f5a6c77f8 |
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -49,7 +49,7 @@ Please ask as many questions as you need, either directly in the issue or on [Di
|
|||||||
6. Build Dashboard `make build-dashboard`
|
6. Build Dashboard `make build-dashboard`
|
||||||
7. Build App `make build-app`
|
7. Build App `make build-app`
|
||||||
8. Build Server `make clean && make`
|
8. Build Server `make clean && make`
|
||||||
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
|
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command. In that case you will have to build `dashboard` & `app` manually using `npm run build` on both dirs.
|
||||||
9. Run binary `./build/server`
|
9. Run binary `./build/server`
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
11
Makefile
11
Makefile
@@ -10,7 +10,16 @@ build-dashboard:
|
|||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf build
|
||||||
test:
|
test:
|
||||||
rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && go test -p 1 -v ./test
|
rm -rf server/test/test.db && rm -rf test.db && cd server && go clean --testcache && TEST_DBS="sqlite" go test -p 1 -v ./test
|
||||||
|
test-all-db:
|
||||||
|
rm -rf server/test/test.db && rm -rf test.db
|
||||||
|
docker run -d --name authorizer_scylla_db -p 9042:9042 scylladb/scylla
|
||||||
|
docker run -d --name authorizer_mongodb_db -p 27017:27017 mongo:4.4.15
|
||||||
|
docker run -d --name authorizer_arangodb -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
|
||||||
|
cd server && go clean --testcache && TEST_DBS="sqlite,mongodb,arangodb,scylladb" go test -p 1 -v ./test
|
||||||
|
docker rm -vf authorizer_mongodb_db
|
||||||
|
docker rm -vf authorizer_scylla_db
|
||||||
|
docker rm -vf authorizer_arangodb
|
||||||
generate:
|
generate:
|
||||||
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate
|
cd server && go get github.com/99designs/gqlgen/cmd@v0.14.0 && go run github.com/99designs/gqlgen generate
|
||||||
|
|
@@ -89,7 +89,7 @@ This guide helps you practice using Authorizer to evaluate it before you use it
|
|||||||
5. Build Dashboard `make build-dashboard`
|
5. Build Dashboard `make build-dashboard`
|
||||||
6. Build App `make build-app`
|
6. Build App `make build-app`
|
||||||
7. Build Server `make clean && make`
|
7. Build Server `make clean && make`
|
||||||
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
|
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command. In that case you will have to build `dashboard` & `app` manually using `npm run build` on both dirs.
|
||||||
8. Run binary `./build/server`
|
8. Run binary `./build/server`
|
||||||
|
|
||||||
### Deploy Authorizer using binaries
|
### Deploy Authorizer using binaries
|
||||||
|
480
dashboard/package-lock.json
generated
480
dashboard/package-lock.json
generated
@@ -17,6 +17,9 @@
|
|||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/react-router-dom": "^5.3.2",
|
"@types/react-router-dom": "^5.3.2",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
|
"draft-js": "^0.11.7",
|
||||||
|
"draft-js-import-html": "^1.4.1",
|
||||||
|
"draftjs-to-html": "^0.9.1",
|
||||||
"esbuild": "^0.14.9",
|
"esbuild": "^0.14.9",
|
||||||
"focus-visible": "^5.2.0",
|
"focus-visible": "^5.2.0",
|
||||||
"framer-motion": "^5.5.5",
|
"framer-motion": "^5.5.5",
|
||||||
@@ -24,11 +27,16 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-draft-wysiwyg": "^1.15.0",
|
||||||
"react-dropzone": "^12.0.4",
|
"react-dropzone": "^12.0.4",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"urql": "^2.0.6"
|
"urql": "^2.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/draftjs-to-html": "^0.8.1",
|
||||||
|
"@types/react-draft-wysiwyg": "^1.13.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -1145,6 +1153,25 @@
|
|||||||
"react-dom": "^16.8.0 || 17.x"
|
"react-dom": "^16.8.0 || 17.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/draft-js": {
|
||||||
|
"version": "0.11.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.9.tgz",
|
||||||
|
"integrity": "sha512-cQJBZjjIlGaPA1tOY+wGz2KhlPtAAZOIXpUvGPxPRw5uzZ2tcj8m6Yu1QDV9YgP36+cqE3cUvgkARBzgUiuI/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"immutable": "~3.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/draftjs-to-html": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/draftjs-to-html/-/draftjs-to-html-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-NBkphQs+qZ/sAz/j1pCUaxkPAOx00LTsE88aMSSfcvK+UfCpjHJDqIMCkm6wKotuJvY5w0BtdRazQ0sAaXzPdg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/draft-js": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/history": {
|
"node_modules/@types/history": {
|
||||||
"version": "4.7.9",
|
"version": "4.7.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
|
||||||
@@ -1191,6 +1218,16 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-draft-wysiwyg": {
|
||||||
|
"version": "1.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-draft-wysiwyg/-/react-draft-wysiwyg-1.13.4.tgz",
|
||||||
|
"integrity": "sha512-wasD1t78JDmQvdPDRPf/mf5FSHMlncunW0F6KMOKB3awzi3Wi21yHMGsRAUOkfTr3R8F+yceG8fSLz0kYWu/QA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/draft-js": "*",
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-router": {
|
"node_modules/@types/react-router": {
|
||||||
"version": "5.1.17",
|
"version": "5.1.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
||||||
@@ -1259,6 +1296,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/asap": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||||
|
},
|
||||||
"node_modules/attr-accept": {
|
"node_modules/attr-accept": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
@@ -1306,6 +1348,11 @@
|
|||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/classnames": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -1340,6 +1387,16 @@
|
|||||||
"toggle-selection": "^1.0.6"
|
"toggle-selection": "^1.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/core-js": {
|
||||||
|
"version": "3.24.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
||||||
|
"integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/core-js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cosmiconfig": {
|
"node_modules/cosmiconfig": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
|
||||||
@@ -1355,6 +1412,14 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "2.6.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/css-box-model": {
|
"node_modules/css-box-model": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||||
@@ -1383,6 +1448,68 @@
|
|||||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/draft-js": {
|
||||||
|
"version": "0.11.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.7.tgz",
|
||||||
|
"integrity": "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg==",
|
||||||
|
"dependencies": {
|
||||||
|
"fbjs": "^2.0.0",
|
||||||
|
"immutable": "~3.7.4",
|
||||||
|
"object-assign": "^4.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=0.14.0",
|
||||||
|
"react-dom": ">=0.14.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/draft-js-import-element": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js-import-element/-/draft-js-import-element-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-WmYT5PrCm47lGL5FkH6sRO3TTAcn7qNHsD3igiPqLG/RXrqyKrqN4+wBgbcT2lhna/yfWTRtgzAbQsSJoS1Meg==",
|
||||||
|
"dependencies": {
|
||||||
|
"draft-js-utils": "^1.4.0",
|
||||||
|
"synthetic-dom": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": ">=0.10.0",
|
||||||
|
"immutable": "3.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/draft-js-import-html": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js-import-html/-/draft-js-import-html-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-KOZmtgxZriCDgg5Smr3Y09TjubvXe7rHPy/2fuLSsL+aSzwUDwH/aHDA/k47U+WfpmL4qgyg4oZhqx9TYJV0tg==",
|
||||||
|
"dependencies": {
|
||||||
|
"draft-js-import-element": "^1.4.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": ">=0.10.0",
|
||||||
|
"immutable": "3.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/draft-js-utils": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-xE81Y+z/muC5D5z9qWmKfxEW1XyXfsBzSbSBk2JRsoD0yzMGGHQm/0MtuqHl/EUDkaBJJLjJ2EACycoDMY/OOg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": ">=0.10.0",
|
||||||
|
"immutable": "3.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/draftjs-to-html": {
|
||||||
|
"version": "0.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/draftjs-to-html/-/draftjs-to-html-0.9.1.tgz",
|
||||||
|
"integrity": "sha512-fFstE6+IayaVFBEvaFt/wN8vdj8FsTRzij7dy7LI9QIwf5LgfHFi9zSpvCg+feJ2tbYVqHxUkjcibwpsTpgFVQ=="
|
||||||
|
},
|
||||||
|
"node_modules/draftjs-utils": {
|
||||||
|
"version": "0.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
|
||||||
|
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": "^0.11.x",
|
||||||
|
"immutable": "3.x.x || 4.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/error-ex": {
|
"node_modules/error-ex": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
@@ -1647,6 +1774,26 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fbjs": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^3.6.4",
|
||||||
|
"cross-fetch": "^3.0.4",
|
||||||
|
"fbjs-css-vars": "^1.0.0",
|
||||||
|
"loose-envify": "^1.0.0",
|
||||||
|
"object-assign": "^4.1.0",
|
||||||
|
"promise": "^7.1.1",
|
||||||
|
"setimmediate": "^1.0.5",
|
||||||
|
"ua-parser-js": "^0.7.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fbjs-css-vars": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
|
||||||
|
},
|
||||||
"node_modules/file-selector": {
|
"node_modules/file-selector": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
||||||
@@ -1802,6 +1949,23 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/html-to-draftjs": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": "^0.10.x || ^0.11.x",
|
||||||
|
"immutable": "3.x.x || 4.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/immutable": {
|
||||||
|
"version": "3.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
|
||||||
|
"integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/import-fresh": {
|
"node_modules/import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@@ -1856,6 +2020,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/linkify-it": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||||
|
"dependencies": {
|
||||||
|
"uc.micro": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@@ -1877,6 +2049,25 @@
|
|||||||
"loose-envify": "cli.js"
|
"loose-envify": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
|
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-assign": {
|
"node_modules/object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -1945,6 +2136,14 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/promise": {
|
||||||
|
"version": "7.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"dependencies": {
|
||||||
|
"asap": "~2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@@ -1991,6 +2190,24 @@
|
|||||||
"react": "17.0.2"
|
"react": "17.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-draft-wysiwyg": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"draftjs-utils": "^0.10.2",
|
||||||
|
"html-to-draftjs": "^1.5.0",
|
||||||
|
"linkify-it": "^2.2.0",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"draft-js": "^0.10.x || ^0.11.x",
|
||||||
|
"immutable": "3.x.x || 4.x.x",
|
||||||
|
"react": "0.13.x || 0.14.x || ^15.0.0-0 || 15.x.x || ^16.0.0-0 || ^16.x.x || ^17.x.x || ^18.x.x",
|
||||||
|
"react-dom": "0.13.x || 0.14.x || ^15.0.0-0 || 15.x.x || ^16.0.0-0 || ^16.x.x || ^17.x.x || ^18.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dropzone": {
|
"node_modules/react-dropzone": {
|
||||||
"version": "12.0.4",
|
"version": "12.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
@@ -2207,6 +2424,11 @@
|
|||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/setimmediate": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
@@ -2240,6 +2462,11 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/synthetic-dom": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/synthetic-dom/-/synthetic-dom-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-mHv51ZsmZ+ShT/4s5kg+MGUIhY7Ltq4v03xpN1c8T1Krb5pScsh/lzEjyhrVD0soVDbThbd2e+4dD9vnDG4rhg=="
|
||||||
|
},
|
||||||
"node_modules/tiny-invariant": {
|
"node_modules/tiny-invariant": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||||
@@ -2258,6 +2485,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
@@ -2275,6 +2507,29 @@
|
|||||||
"node": ">=4.2.0"
|
"node": ">=4.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ua-parser-js": {
|
||||||
|
"version": "0.7.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
||||||
|
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ua-parser-js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paypal",
|
||||||
|
"url": "https://paypal.me/faisalman"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uc.micro": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
|
||||||
|
},
|
||||||
"node_modules/urql": {
|
"node_modules/urql": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
||||||
@@ -2333,6 +2588,20 @@
|
|||||||
"loose-envify": "^1.0.0"
|
"loose-envify": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wonka": {
|
"node_modules/wonka": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",
|
||||||
@@ -2529,8 +2798,7 @@
|
|||||||
"@chakra-ui/css-reset": {
|
"@chakra-ui/css-reset": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.1.1.tgz",
|
||||||
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg==",
|
"integrity": "sha512-+KNNHL4OWqeKia5SL858K3Qbd8WxMij9mWIilBzLD4j2KFrl/+aWFw8syMKth3NmgIibrjsljo+PU3fy2o50dg=="
|
||||||
"requires": {}
|
|
||||||
},
|
},
|
||||||
"@chakra-ui/descendant": {
|
"@chakra-ui/descendant": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
@@ -3134,8 +3402,7 @@
|
|||||||
"@graphql-typed-document-node/core": {
|
"@graphql-typed-document-node/core": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.1.tgz",
|
||||||
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg==",
|
"integrity": "sha512-NQ17ii0rK1b34VZonlmT2QMJFI70m0TRwbknO/ihlbatXyaktDhN/98vBiUU6kNBPljqGqyIrl2T4nY2RpFANg=="
|
||||||
"requires": {}
|
|
||||||
},
|
},
|
||||||
"@popperjs/core": {
|
"@popperjs/core": {
|
||||||
"version": "2.11.0",
|
"version": "2.11.0",
|
||||||
@@ -3172,6 +3439,25 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/draft-js": {
|
||||||
|
"version": "0.11.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/draft-js/-/draft-js-0.11.9.tgz",
|
||||||
|
"integrity": "sha512-cQJBZjjIlGaPA1tOY+wGz2KhlPtAAZOIXpUvGPxPRw5uzZ2tcj8m6Yu1QDV9YgP36+cqE3cUvgkARBzgUiuI/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"immutable": "~3.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/draftjs-to-html": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/draftjs-to-html/-/draftjs-to-html-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-NBkphQs+qZ/sAz/j1pCUaxkPAOx00LTsE88aMSSfcvK+UfCpjHJDqIMCkm6wKotuJvY5w0BtdRazQ0sAaXzPdg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/draft-js": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/history": {
|
"@types/history": {
|
||||||
"version": "4.7.9",
|
"version": "4.7.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.9.tgz",
|
||||||
@@ -3218,6 +3504,16 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-draft-wysiwyg": {
|
||||||
|
"version": "1.13.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-draft-wysiwyg/-/react-draft-wysiwyg-1.13.4.tgz",
|
||||||
|
"integrity": "sha512-wasD1t78JDmQvdPDRPf/mf5FSHMlncunW0F6KMOKB3awzi3Wi21yHMGsRAUOkfTr3R8F+yceG8fSLz0kYWu/QA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/draft-js": "*",
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-router": {
|
"@types/react-router": {
|
||||||
"version": "5.1.17",
|
"version": "5.1.17",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.17.tgz",
|
||||||
@@ -3279,6 +3575,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"asap": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||||
|
},
|
||||||
"attr-accept": {
|
"attr-accept": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz",
|
||||||
@@ -3316,6 +3617,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"classnames": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA=="
|
||||||
|
},
|
||||||
"color-convert": {
|
"color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
@@ -3350,6 +3656,11 @@
|
|||||||
"toggle-selection": "^1.0.6"
|
"toggle-selection": "^1.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"core-js": {
|
||||||
|
"version": "3.24.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.24.1.tgz",
|
||||||
|
"integrity": "sha512-0QTBSYSUZ6Gq21utGzkfITDylE8jWC9Ne1D2MrhvlsZBI1x39OdDIVbzSqtgMndIy6BlHxBXpMGqzZmnztg2rg=="
|
||||||
|
},
|
||||||
"cosmiconfig": {
|
"cosmiconfig": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
|
||||||
@@ -3362,6 +3673,14 @@
|
|||||||
"yaml": "^1.7.2"
|
"yaml": "^1.7.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cross-fetch": {
|
||||||
|
"version": "3.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||||
|
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||||
|
"requires": {
|
||||||
|
"node-fetch": "2.6.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"css-box-model": {
|
"css-box-model": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz",
|
||||||
@@ -3390,6 +3709,48 @@
|
|||||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||||
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
|
||||||
},
|
},
|
||||||
|
"draft-js": {
|
||||||
|
"version": "0.11.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.7.tgz",
|
||||||
|
"integrity": "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg==",
|
||||||
|
"requires": {
|
||||||
|
"fbjs": "^2.0.0",
|
||||||
|
"immutable": "~3.7.4",
|
||||||
|
"object-assign": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draft-js-import-element": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js-import-element/-/draft-js-import-element-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-WmYT5PrCm47lGL5FkH6sRO3TTAcn7qNHsD3igiPqLG/RXrqyKrqN4+wBgbcT2lhna/yfWTRtgzAbQsSJoS1Meg==",
|
||||||
|
"requires": {
|
||||||
|
"draft-js-utils": "^1.4.0",
|
||||||
|
"synthetic-dom": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draft-js-import-html": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js-import-html/-/draft-js-import-html-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-KOZmtgxZriCDgg5Smr3Y09TjubvXe7rHPy/2fuLSsL+aSzwUDwH/aHDA/k47U+WfpmL4qgyg4oZhqx9TYJV0tg==",
|
||||||
|
"requires": {
|
||||||
|
"draft-js-import-element": "^1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"draft-js-utils": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/draft-js-utils/-/draft-js-utils-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-xE81Y+z/muC5D5z9qWmKfxEW1XyXfsBzSbSBk2JRsoD0yzMGGHQm/0MtuqHl/EUDkaBJJLjJ2EACycoDMY/OOg=="
|
||||||
|
},
|
||||||
|
"draftjs-to-html": {
|
||||||
|
"version": "0.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/draftjs-to-html/-/draftjs-to-html-0.9.1.tgz",
|
||||||
|
"integrity": "sha512-fFstE6+IayaVFBEvaFt/wN8vdj8FsTRzij7dy7LI9QIwf5LgfHFi9zSpvCg+feJ2tbYVqHxUkjcibwpsTpgFVQ=="
|
||||||
|
},
|
||||||
|
"draftjs-utils": {
|
||||||
|
"version": "0.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/draftjs-utils/-/draftjs-utils-0.10.2.tgz",
|
||||||
|
"integrity": "sha512-EstHqr3R3JVcilJrBaO/A+01GvwwKmC7e4TCjC7S94ZeMh4IVmf60OuQXtHHpwItK8C2JCi3iljgN5KHkJboUg=="
|
||||||
|
},
|
||||||
"error-ex": {
|
"error-ex": {
|
||||||
"version": "1.3.2",
|
"version": "1.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||||
@@ -3536,6 +3897,26 @@
|
|||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||||
},
|
},
|
||||||
|
"fbjs": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ==",
|
||||||
|
"requires": {
|
||||||
|
"core-js": "^3.6.4",
|
||||||
|
"cross-fetch": "^3.0.4",
|
||||||
|
"fbjs-css-vars": "^1.0.0",
|
||||||
|
"loose-envify": "^1.0.0",
|
||||||
|
"object-assign": "^4.1.0",
|
||||||
|
"promise": "^7.1.1",
|
||||||
|
"setimmediate": "^1.0.5",
|
||||||
|
"ua-parser-js": "^0.7.18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fbjs-css-vars": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="
|
||||||
|
},
|
||||||
"file-selector": {
|
"file-selector": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz",
|
||||||
@@ -3659,6 +4040,16 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"html-to-draftjs": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/html-to-draftjs/-/html-to-draftjs-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-kggLXBNciKDwKf+KYsuE+V5gw4dZ7nHyGMX9m0wy7urzWjKGWyNFetmArRLvRV0VrxKN70WylFsJvMTJx02OBQ=="
|
||||||
|
},
|
||||||
|
"immutable": {
|
||||||
|
"version": "3.7.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
|
||||||
|
"integrity": "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw=="
|
||||||
|
},
|
||||||
"import-fresh": {
|
"import-fresh": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||||
@@ -3704,6 +4095,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||||
},
|
},
|
||||||
|
"linkify-it": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-GnAl/knGn+i1U/wjBz3akz2stz+HrHLsxMwHQGofCDfPvlf+gDKN58UtfmUquTY4/MXeE2x7k19KQmeoZi94Iw==",
|
||||||
|
"requires": {
|
||||||
|
"uc.micro": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@@ -3722,6 +4121,14 @@
|
|||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||||
|
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||||
|
"requires": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"object-assign": {
|
"object-assign": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||||
@@ -3777,6 +4184,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"promise": {
|
||||||
|
"version": "7.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"requires": {
|
||||||
|
"asap": "~2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@@ -3814,6 +4229,18 @@
|
|||||||
"scheduler": "^0.20.2"
|
"scheduler": "^0.20.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-draft-wysiwyg": {
|
||||||
|
"version": "1.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-draft-wysiwyg/-/react-draft-wysiwyg-1.15.0.tgz",
|
||||||
|
"integrity": "sha512-p1cYZcWc6/ALFBVksbFoCM3b29fGQDlZLIMrXng0TU/UElxIOF2/AWWo4L5auIYVhmqKTZ0NkNjnXOzGGuxyeA==",
|
||||||
|
"requires": {
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"draftjs-utils": "^0.10.2",
|
||||||
|
"html-to-draftjs": "^1.5.0",
|
||||||
|
"linkify-it": "^2.2.0",
|
||||||
|
"prop-types": "^15.7.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-dropzone": {
|
"react-dropzone": {
|
||||||
"version": "12.0.4",
|
"version": "12.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.0.4.tgz",
|
||||||
@@ -3845,8 +4272,7 @@
|
|||||||
"react-icons": {
|
"react-icons": {
|
||||||
"version": "4.3.1",
|
"version": "4.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz",
|
||||||
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==",
|
"integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ=="
|
||||||
"requires": {}
|
|
||||||
},
|
},
|
||||||
"react-is": {
|
"react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
@@ -3968,6 +4394,11 @@
|
|||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"setimmediate": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
|
||||||
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.5.7",
|
"version": "0.5.7",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||||
@@ -3995,6 +4426,11 @@
|
|||||||
"has-flag": "^3.0.0"
|
"has-flag": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"synthetic-dom": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/synthetic-dom/-/synthetic-dom-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-mHv51ZsmZ+ShT/4s5kg+MGUIhY7Ltq4v03xpN1c8T1Krb5pScsh/lzEjyhrVD0soVDbThbd2e+4dD9vnDG4rhg=="
|
||||||
|
},
|
||||||
"tiny-invariant": {
|
"tiny-invariant": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz",
|
||||||
@@ -4010,6 +4446,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
|
||||||
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
"integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI="
|
||||||
},
|
},
|
||||||
|
"tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||||
|
},
|
||||||
"tslib": {
|
"tslib": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||||
@@ -4020,6 +4461,16 @@
|
|||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz",
|
||||||
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg=="
|
"integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg=="
|
||||||
},
|
},
|
||||||
|
"ua-parser-js": {
|
||||||
|
"version": "0.7.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz",
|
||||||
|
"integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ=="
|
||||||
|
},
|
||||||
|
"uc.micro": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
|
||||||
|
},
|
||||||
"urql": {
|
"urql": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/urql/-/urql-2.0.6.tgz",
|
||||||
@@ -4032,8 +4483,7 @@
|
|||||||
"use-callback-ref": {
|
"use-callback-ref": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.5.tgz",
|
||||||
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==",
|
"integrity": "sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg=="
|
||||||
"requires": {}
|
|
||||||
},
|
},
|
||||||
"use-sidecar": {
|
"use-sidecar": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
@@ -4059,6 +4509,20 @@
|
|||||||
"loose-envify": "^1.0.0"
|
"loose-envify": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
|
},
|
||||||
|
"whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"requires": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"wonka": {
|
"wonka": {
|
||||||
"version": "4.0.15",
|
"version": "4.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/wonka/-/wonka-4.0.15.tgz",
|
||||||
|
@@ -19,6 +19,9 @@
|
|||||||
"@types/react-dom": "^17.0.11",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/react-router-dom": "^5.3.2",
|
"@types/react-router-dom": "^5.3.2",
|
||||||
"dayjs": "^1.10.7",
|
"dayjs": "^1.10.7",
|
||||||
|
"draft-js": "^0.11.7",
|
||||||
|
"draft-js-import-html": "^1.4.1",
|
||||||
|
"draftjs-to-html": "^0.9.1",
|
||||||
"esbuild": "^0.14.9",
|
"esbuild": "^0.14.9",
|
||||||
"focus-visible": "^5.2.0",
|
"focus-visible": "^5.2.0",
|
||||||
"framer-motion": "^5.5.5",
|
"framer-motion": "^5.5.5",
|
||||||
@@ -26,10 +29,15 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-draft-wysiwyg": "^1.15.0",
|
||||||
"react-dropzone": "^12.0.4",
|
"react-dropzone": "^12.0.4",
|
||||||
"react-icons": "^4.3.1",
|
"react-icons": "^4.3.1",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.2.1",
|
||||||
"typescript": "^4.5.4",
|
"typescript": "^4.5.4",
|
||||||
"urql": "^2.0.6"
|
"urql": "^2.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/draftjs-to-html": "^0.8.1",
|
||||||
|
"@types/react-draft-wysiwyg": "^1.13.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
106
dashboard/src/components/DeleteEmailTemplateModal.tsx
Normal file
106
dashboard/src/components/DeleteEmailTemplateModal.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaRegTrashAlt } from 'react-icons/fa';
|
||||||
|
import { DeleteEmailTemplate } from '../graphql/mutation';
|
||||||
|
import { capitalizeFirstLetter } from '../utils';
|
||||||
|
|
||||||
|
interface deleteEmailTemplateModalInputPropTypes {
|
||||||
|
emailTemplateId: string;
|
||||||
|
eventName: string;
|
||||||
|
fetchEmailTemplatesData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteEmailTemplateModal = ({
|
||||||
|
emailTemplateId,
|
||||||
|
eventName,
|
||||||
|
fetchEmailTemplatesData,
|
||||||
|
}: deleteEmailTemplateModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
const deleteHandler = async () => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(DeleteEmailTemplate, { params: { id: emailTemplateId } })
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (res.data?._delete_email_template) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.data?._delete_email_template.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
fetchEmailTemplatesData();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>Delete</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Delete Email Template</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Text fontSize="md">Are you sure?</Text>
|
||||||
|
<Flex
|
||||||
|
padding="5%"
|
||||||
|
marginTop="5%"
|
||||||
|
marginBottom="2%"
|
||||||
|
border="1px solid #ff7875"
|
||||||
|
borderRadius="5px"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Email template for event <b>{eventName}</b> will be deleted
|
||||||
|
permanently!
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaRegTrashAlt />}
|
||||||
|
colorScheme="red"
|
||||||
|
variant="solid"
|
||||||
|
onClick={deleteHandler}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Delete
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteEmailTemplateModal;
|
106
dashboard/src/components/DeleteWebhookModal.tsx
Normal file
106
dashboard/src/components/DeleteWebhookModal.tsx
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { FaRegTrashAlt } from 'react-icons/fa';
|
||||||
|
import { DeleteWebhook } from '../graphql/mutation';
|
||||||
|
import { capitalizeFirstLetter } from '../utils';
|
||||||
|
|
||||||
|
interface deleteWebhookModalInputPropTypes {
|
||||||
|
webhookId: string;
|
||||||
|
eventName: string;
|
||||||
|
fetchWebookData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeleteWebhookModal = ({
|
||||||
|
webhookId,
|
||||||
|
eventName,
|
||||||
|
fetchWebookData,
|
||||||
|
}: deleteWebhookModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
|
const deleteHandler = async () => {
|
||||||
|
const res = await client
|
||||||
|
.mutation(DeleteWebhook, { params: { id: webhookId } })
|
||||||
|
.toPromise();
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
} else if (res.data?._delete_webhook) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.data?._delete_webhook.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
fetchWebookData();
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>Delete</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Delete Webhook</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Text fontSize="md">Are you sure?</Text>
|
||||||
|
<Flex
|
||||||
|
padding="5%"
|
||||||
|
marginTop="5%"
|
||||||
|
marginBottom="2%"
|
||||||
|
border="1px solid #ff7875"
|
||||||
|
borderRadius="5px"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm">
|
||||||
|
Webhook for event <b>{eventName}</b> will be deleted
|
||||||
|
permanently!
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaRegTrashAlt />}
|
||||||
|
colorScheme="red"
|
||||||
|
variant="solid"
|
||||||
|
onClick={deleteHandler}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Delete
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteWebhookModal;
|
@@ -12,7 +12,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Textarea,
|
Textarea,
|
||||||
Switch,
|
Switch,
|
||||||
Code,
|
|
||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
|
@@ -30,6 +30,8 @@ import {
|
|||||||
FiMenu,
|
FiMenu,
|
||||||
FiUsers,
|
FiUsers,
|
||||||
FiChevronDown,
|
FiChevronDown,
|
||||||
|
FiLink,
|
||||||
|
FiFileText,
|
||||||
} from 'react-icons/fi';
|
} from 'react-icons/fi';
|
||||||
import { BiCustomize } from 'react-icons/bi';
|
import { BiCustomize } from 'react-icons/bi';
|
||||||
import { AiOutlineKey } from 'react-icons/ai';
|
import { AiOutlineKey } from 'react-icons/ai';
|
||||||
@@ -111,6 +113,8 @@ const LinkItems: Array<LinkItemProps> = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ name: 'Users', icon: FiUsers, route: '/users' },
|
{ name: 'Users', icon: FiUsers, route: '/users' },
|
||||||
|
{ name: 'Webhooks', icon: FiLink, route: '/webhooks' },
|
||||||
|
{ name: 'Email Templates', icon: FiFileText, route: '/email-templates' },
|
||||||
];
|
];
|
||||||
|
|
||||||
interface SidebarProps extends BoxProps {
|
interface SidebarProps extends BoxProps {
|
||||||
|
375
dashboard/src/components/UpdateEmailTemplateModal.tsx
Normal file
375
dashboard/src/components/UpdateEmailTemplateModal.tsx
Normal file
@@ -0,0 +1,375 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Select,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { FaPlus } from 'react-icons/fa';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import { Editor } from 'react-draft-wysiwyg';
|
||||||
|
import { EditorState, convertToRaw, Modifier } from 'draft-js';
|
||||||
|
import draftToHtml from 'draftjs-to-html';
|
||||||
|
import { stateFromHTML } from 'draft-js-import-html';
|
||||||
|
import {
|
||||||
|
UpdateModalViews,
|
||||||
|
EmailTemplateInputDataFields,
|
||||||
|
emailTemplateEventNames,
|
||||||
|
emailTemplateVariables,
|
||||||
|
} from '../constants';
|
||||||
|
import { capitalizeFirstLetter } from '../utils';
|
||||||
|
import { AddEmailTemplate, EditEmailTemplate } from '../graphql/mutation';
|
||||||
|
|
||||||
|
interface selectedEmailTemplateDataTypes {
|
||||||
|
[EmailTemplateInputDataFields.ID]: string;
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]: string;
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: string;
|
||||||
|
[EmailTemplateInputDataFields.CREATED_AT]: number;
|
||||||
|
[EmailTemplateInputDataFields.TEMPLATE]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateEmailTemplateInputPropTypes {
|
||||||
|
view: UpdateModalViews;
|
||||||
|
selectedTemplate?: selectedEmailTemplateDataTypes;
|
||||||
|
fetchEmailTemplatesData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface templateVariableDataTypes {
|
||||||
|
text: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface emailTemplateDataType {
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]: string;
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface validatorDataType {
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initTemplateData: emailTemplateDataType = {
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]:
|
||||||
|
emailTemplateEventNames.BASIC_AUTH_SIGNUP,
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const initTemplateValidatorData: validatorDataType = {
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateEmailTemplate = ({
|
||||||
|
view,
|
||||||
|
selectedTemplate,
|
||||||
|
fetchEmailTemplatesData,
|
||||||
|
}: UpdateEmailTemplateInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [editorState, setEditorState] = React.useState<EditorState>(
|
||||||
|
EditorState.createEmpty()
|
||||||
|
);
|
||||||
|
const [templateVariables, setTemplateVariables] = useState<
|
||||||
|
templateVariableDataTypes[]
|
||||||
|
>([]);
|
||||||
|
const [templateData, setTemplateData] = useState<emailTemplateDataType>({
|
||||||
|
...initTemplateData,
|
||||||
|
});
|
||||||
|
const [validator, setValidator] = useState<validatorDataType>({
|
||||||
|
...initTemplateValidatorData,
|
||||||
|
});
|
||||||
|
const onEditorStateChange = (editorState: EditorState) => {
|
||||||
|
setEditorState(editorState);
|
||||||
|
};
|
||||||
|
const inputChangehandler = (inputType: string, value: any) => {
|
||||||
|
if (inputType !== EmailTemplateInputDataFields.EVENT_NAME) {
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[inputType]: value?.trim().length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTemplateData({ ...templateData, [inputType]: value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateData = () => {
|
||||||
|
const rawData: string = draftToHtml(
|
||||||
|
convertToRaw(editorState.getCurrentContent())
|
||||||
|
).trim();
|
||||||
|
return (
|
||||||
|
!loading &&
|
||||||
|
rawData &&
|
||||||
|
rawData !== '<p></p>' &&
|
||||||
|
rawData !== '<h1></h1>' &&
|
||||||
|
templateData[EmailTemplateInputDataFields.EVENT_NAME].length > 0 &&
|
||||||
|
templateData[EmailTemplateInputDataFields.SUBJECT].length > 0 &&
|
||||||
|
validator[EmailTemplateInputDataFields.SUBJECT]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveData = async () => {
|
||||||
|
if (!validateData()) return;
|
||||||
|
setLoading(true);
|
||||||
|
const params = {
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]:
|
||||||
|
templateData[EmailTemplateInputDataFields.EVENT_NAME],
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]:
|
||||||
|
templateData[EmailTemplateInputDataFields.SUBJECT],
|
||||||
|
[EmailTemplateInputDataFields.TEMPLATE]: draftToHtml(
|
||||||
|
convertToRaw(editorState.getCurrentContent())
|
||||||
|
).trim(),
|
||||||
|
};
|
||||||
|
let res: any = {};
|
||||||
|
if (
|
||||||
|
view === UpdateModalViews.Edit &&
|
||||||
|
selectedTemplate?.[EmailTemplateInputDataFields.ID]
|
||||||
|
) {
|
||||||
|
res = await client
|
||||||
|
.mutation(EditEmailTemplate, {
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
id: selectedTemplate[EmailTemplateInputDataFields.ID],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
} else {
|
||||||
|
res = await client.mutation(AddEmailTemplate, { params }).toPromise();
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else if (
|
||||||
|
res.data?._add_email_template ||
|
||||||
|
res.data?._update_email_template
|
||||||
|
) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(
|
||||||
|
res.data?._add_email_template?.message ||
|
||||||
|
res.data?._update_email_template?.message
|
||||||
|
),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
setTemplateData({
|
||||||
|
...initTemplateData,
|
||||||
|
});
|
||||||
|
setValidator({ ...initTemplateValidatorData });
|
||||||
|
fetchEmailTemplatesData();
|
||||||
|
}
|
||||||
|
view === UpdateModalViews.ADD && onClose();
|
||||||
|
};
|
||||||
|
const resetData = () => {
|
||||||
|
if (selectedTemplate) {
|
||||||
|
setTemplateData(selectedTemplate);
|
||||||
|
setEditorState(
|
||||||
|
EditorState.createWithContent(stateFromHTML(selectedTemplate.template))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setTemplateData({ ...initTemplateData });
|
||||||
|
setEditorState(EditorState.createEmpty());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isOpen &&
|
||||||
|
view === UpdateModalViews.Edit &&
|
||||||
|
selectedTemplate &&
|
||||||
|
Object.keys(selectedTemplate || {}).length
|
||||||
|
) {
|
||||||
|
const { id, created_at, template, ...rest } = selectedTemplate;
|
||||||
|
setTemplateData(rest);
|
||||||
|
setEditorState(EditorState.createWithContent(stateFromHTML(template)));
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
useEffect(() => {
|
||||||
|
const updatedTemplateVariables = Object.entries(
|
||||||
|
emailTemplateVariables
|
||||||
|
).reduce((acc, varData): any => {
|
||||||
|
if (
|
||||||
|
(templateData[EmailTemplateInputDataFields.EVENT_NAME] !==
|
||||||
|
emailTemplateEventNames.VERIFY_OTP &&
|
||||||
|
varData[1] === emailTemplateVariables.otp) ||
|
||||||
|
(templateData[EmailTemplateInputDataFields.EVENT_NAME] ===
|
||||||
|
emailTemplateEventNames.VERIFY_OTP &&
|
||||||
|
varData[1] === emailTemplateVariables.verification_url)
|
||||||
|
) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
...acc,
|
||||||
|
{
|
||||||
|
text: varData[0],
|
||||||
|
value: varData[1],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, []);
|
||||||
|
setTemplateVariables(updatedTemplateVariables);
|
||||||
|
}, [templateData[EmailTemplateInputDataFields.EVENT_NAME]]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{view === UpdateModalViews.ADD ? (
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onOpen}
|
||||||
|
isDisabled={false}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Center h="100%">Add Template</Center>{' '}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<MenuItem onClick={onOpen}>Edit</MenuItem>
|
||||||
|
)}
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} size="3xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
{view === UpdateModalViews.ADD
|
||||||
|
? 'Add New Email Template'
|
||||||
|
: 'Edit Email Template'}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
border="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor="gray.200"
|
||||||
|
p="5"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Event Name</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<Select
|
||||||
|
size="md"
|
||||||
|
value={
|
||||||
|
templateData[EmailTemplateInputDataFields.EVENT_NAME]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
EmailTemplateInputDataFields.EVENT_NAME,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{Object.entries(emailTemplateEventNames).map(
|
||||||
|
([key, value]: any) => (
|
||||||
|
<option value={value} key={key}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="5%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Subject</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<InputGroup size="md">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="Subject Line"
|
||||||
|
value={templateData[EmailTemplateInputDataFields.SUBJECT]}
|
||||||
|
isInvalid={
|
||||||
|
!validator[EmailTemplateInputDataFields.SUBJECT]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
EmailTemplateInputDataFields.SUBJECT,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex>Template Body</Flex>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
fontSize: 14,
|
||||||
|
}}
|
||||||
|
color="gray.400"
|
||||||
|
>{`To select dynamic variables open curly braces "{"`}</Text>
|
||||||
|
</Flex>
|
||||||
|
<Editor
|
||||||
|
editorState={editorState}
|
||||||
|
onEditorStateChange={onEditorStateChange}
|
||||||
|
editorStyle={{
|
||||||
|
border: '1px solid #d9d9d9',
|
||||||
|
borderRadius: '5px',
|
||||||
|
marginTop: '2%',
|
||||||
|
height: '35vh',
|
||||||
|
}}
|
||||||
|
mention={{
|
||||||
|
separator: ' ',
|
||||||
|
trigger: '{',
|
||||||
|
suggestions: templateVariables,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={resetData}
|
||||||
|
isDisabled={loading}
|
||||||
|
marginRight="5"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
isLoading={loading}
|
||||||
|
onClick={saveData}
|
||||||
|
isDisabled={!validateData()}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Save
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateEmailTemplate;
|
667
dashboard/src/components/UpdateWebhookModal.tsx
Normal file
667
dashboard/src/components/UpdateWebhookModal.tsx
Normal file
@@ -0,0 +1,667 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Code,
|
||||||
|
Collapse,
|
||||||
|
Flex,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputRightElement,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaAngleDown,
|
||||||
|
FaAngleUp,
|
||||||
|
FaMinusCircle,
|
||||||
|
FaPlus,
|
||||||
|
FaRegClone,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
webhookEventNames,
|
||||||
|
ArrayInputOperations,
|
||||||
|
WebhookInputDataFields,
|
||||||
|
WebhookInputHeaderFields,
|
||||||
|
UpdateModalViews,
|
||||||
|
webhookVerifiedStatus,
|
||||||
|
webhookPayloadExample,
|
||||||
|
} from '../constants';
|
||||||
|
import {
|
||||||
|
capitalizeFirstLetter,
|
||||||
|
copyTextToClipboard,
|
||||||
|
validateURI,
|
||||||
|
} from '../utils';
|
||||||
|
import { AddWebhook, EditWebhook, TestEndpoint } from '../graphql/mutation';
|
||||||
|
import { BiCheckCircle, BiError, BiErrorCircle } from 'react-icons/bi';
|
||||||
|
|
||||||
|
interface headersDataType {
|
||||||
|
[WebhookInputHeaderFields.KEY]: string;
|
||||||
|
[WebhookInputHeaderFields.VALUE]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface headersValidatorDataType {
|
||||||
|
[WebhookInputHeaderFields.KEY]: boolean;
|
||||||
|
[WebhookInputHeaderFields.VALUE]: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface selecetdWebhookDataTypes {
|
||||||
|
[WebhookInputDataFields.ID]: string;
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: string;
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: string;
|
||||||
|
[WebhookInputDataFields.ENABLED]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UpdateWebhookModalInputPropTypes {
|
||||||
|
view: UpdateModalViews;
|
||||||
|
selectedWebhook?: selecetdWebhookDataTypes;
|
||||||
|
fetchWebookData: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const initHeadersData: headersDataType = {
|
||||||
|
[WebhookInputHeaderFields.KEY]: '',
|
||||||
|
[WebhookInputHeaderFields.VALUE]: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const initHeadersValidatorData: headersValidatorDataType = {
|
||||||
|
[WebhookInputHeaderFields.KEY]: true,
|
||||||
|
[WebhookInputHeaderFields.VALUE]: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
interface webhookDataType {
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: string;
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: string;
|
||||||
|
[WebhookInputDataFields.ENABLED]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]: headersDataType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface validatorDataType {
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]: headersValidatorDataType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const initWebhookData: webhookDataType = {
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: webhookEventNames.USER_LOGIN,
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: '',
|
||||||
|
[WebhookInputDataFields.ENABLED]: true,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const initWebhookValidatorData: validatorDataType = {
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: true,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersValidatorData }],
|
||||||
|
};
|
||||||
|
|
||||||
|
const UpdateWebhookModal = ({
|
||||||
|
view,
|
||||||
|
selectedWebhook,
|
||||||
|
fetchWebookData,
|
||||||
|
}: UpdateWebhookModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const toast = useToast();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [verifyingEndpoint, setVerifyingEndpoint] = useState<boolean>(false);
|
||||||
|
const [isShowingPayload, setIsShowingPayload] = useState<boolean>(false);
|
||||||
|
const [webhook, setWebhook] = useState<webhookDataType>({
|
||||||
|
...initWebhookData,
|
||||||
|
});
|
||||||
|
const [validator, setValidator] = useState<validatorDataType>({
|
||||||
|
...initWebhookValidatorData,
|
||||||
|
});
|
||||||
|
const [verifiedStatus, setVerifiedStatus] = useState<webhookVerifiedStatus>(
|
||||||
|
webhookVerifiedStatus.PENDING
|
||||||
|
);
|
||||||
|
const inputChangehandler = (
|
||||||
|
inputType: string,
|
||||||
|
value: any,
|
||||||
|
headerInputType: string = WebhookInputHeaderFields.KEY,
|
||||||
|
headerIndex: number = 0
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
verifiedStatus !== webhookVerifiedStatus.PENDING &&
|
||||||
|
inputType !== WebhookInputDataFields.ENABLED
|
||||||
|
) {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.PENDING);
|
||||||
|
}
|
||||||
|
switch (inputType) {
|
||||||
|
case WebhookInputDataFields.EVENT_NAME:
|
||||||
|
setWebhook({ ...webhook, [inputType]: value });
|
||||||
|
break;
|
||||||
|
case WebhookInputDataFields.ENDPOINT:
|
||||||
|
setWebhook({ ...webhook, [inputType]: value });
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: validateURI(value),
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case WebhookInputDataFields.ENABLED:
|
||||||
|
setWebhook({ ...webhook, [inputType]: value });
|
||||||
|
break;
|
||||||
|
case WebhookInputDataFields.HEADERS:
|
||||||
|
const updatedHeaders: any = [
|
||||||
|
...webhook[WebhookInputDataFields.HEADERS],
|
||||||
|
];
|
||||||
|
const updatedHeadersValidatorData: any = [
|
||||||
|
...validator[WebhookInputDataFields.HEADERS],
|
||||||
|
];
|
||||||
|
const otherHeaderInputType =
|
||||||
|
headerInputType === WebhookInputHeaderFields.KEY
|
||||||
|
? WebhookInputHeaderFields.VALUE
|
||||||
|
: WebhookInputHeaderFields.KEY;
|
||||||
|
updatedHeaders[headerIndex][headerInputType] = value;
|
||||||
|
updatedHeadersValidatorData[headerIndex][headerInputType] =
|
||||||
|
value.length > 0
|
||||||
|
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
|
||||||
|
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
|
||||||
|
updatedHeadersValidatorData[headerIndex][otherHeaderInputType] =
|
||||||
|
value.length > 0
|
||||||
|
? updatedHeaders[headerIndex][otherHeaderInputType].length > 0
|
||||||
|
: updatedHeaders[headerIndex][otherHeaderInputType].length === 0;
|
||||||
|
setWebhook({ ...webhook, [inputType]: updatedHeaders });
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[inputType]: updatedHeadersValidatorData,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const updateHeaders = (operation: string, index: number = 0) => {
|
||||||
|
if (verifiedStatus !== webhookVerifiedStatus.PENDING) {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.PENDING);
|
||||||
|
}
|
||||||
|
switch (operation) {
|
||||||
|
case ArrayInputOperations.APPEND:
|
||||||
|
setWebhook({
|
||||||
|
...webhook,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [
|
||||||
|
...(webhook?.[WebhookInputDataFields.HEADERS] || []),
|
||||||
|
{ ...initHeadersData },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [
|
||||||
|
...(validator?.[WebhookInputDataFields.HEADERS] || []),
|
||||||
|
{ ...initHeadersValidatorData },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case ArrayInputOperations.REMOVE:
|
||||||
|
if (webhook?.[WebhookInputDataFields.HEADERS]?.length) {
|
||||||
|
const updatedHeaders = [...webhook[WebhookInputDataFields.HEADERS]];
|
||||||
|
updatedHeaders.splice(index, 1);
|
||||||
|
setWebhook({
|
||||||
|
...webhook,
|
||||||
|
[WebhookInputDataFields.HEADERS]: updatedHeaders,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (validator?.[WebhookInputDataFields.HEADERS]?.length) {
|
||||||
|
const updatedHeadersData = [
|
||||||
|
...validator[WebhookInputDataFields.HEADERS],
|
||||||
|
];
|
||||||
|
updatedHeadersData.splice(index, 1);
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.HEADERS]: updatedHeadersData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const validateData = () => {
|
||||||
|
return (
|
||||||
|
!loading &&
|
||||||
|
!verifyingEndpoint &&
|
||||||
|
webhook[WebhookInputDataFields.EVENT_NAME].length > 0 &&
|
||||||
|
webhook[WebhookInputDataFields.ENDPOINT].length > 0 &&
|
||||||
|
validator[WebhookInputDataFields.ENDPOINT] &&
|
||||||
|
!validator[WebhookInputDataFields.HEADERS].some(
|
||||||
|
(headerData: headersValidatorDataType) =>
|
||||||
|
!headerData.key || !headerData.value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const getParams = () => {
|
||||||
|
let params: any = {
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]:
|
||||||
|
webhook[WebhookInputDataFields.EVENT_NAME],
|
||||||
|
[WebhookInputDataFields.ENDPOINT]:
|
||||||
|
webhook[WebhookInputDataFields.ENDPOINT],
|
||||||
|
[WebhookInputDataFields.ENABLED]: webhook[WebhookInputDataFields.ENABLED],
|
||||||
|
[WebhookInputDataFields.HEADERS]: {},
|
||||||
|
};
|
||||||
|
if (webhook[WebhookInputDataFields.HEADERS].length) {
|
||||||
|
const headers = webhook[WebhookInputDataFields.HEADERS].reduce(
|
||||||
|
(acc, data) => {
|
||||||
|
return data.key ? { ...acc, [data.key]: data.value } : acc;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
if (Object.keys(headers).length) {
|
||||||
|
params[WebhookInputDataFields.HEADERS] = headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
const saveData = async () => {
|
||||||
|
if (!validateData()) return;
|
||||||
|
setLoading(true);
|
||||||
|
const params = getParams();
|
||||||
|
let res: any = {};
|
||||||
|
if (
|
||||||
|
view === UpdateModalViews.Edit &&
|
||||||
|
selectedWebhook?.[WebhookInputDataFields.ID]
|
||||||
|
) {
|
||||||
|
res = await client
|
||||||
|
.mutation(EditWebhook, {
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
id: selectedWebhook[WebhookInputDataFields.ID],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
} else {
|
||||||
|
res = await client.mutation(AddWebhook, { params }).toPromise();
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
if (res.error) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(res.error.message),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'error',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
} else if (res.data?._add_webhook || res.data?._update_webhook) {
|
||||||
|
toast({
|
||||||
|
title: capitalizeFirstLetter(
|
||||||
|
res.data?._add_webhook?.message || res.data?._update_webhook?.message
|
||||||
|
),
|
||||||
|
isClosable: true,
|
||||||
|
status: 'success',
|
||||||
|
position: 'bottom-right',
|
||||||
|
});
|
||||||
|
setWebhook({
|
||||||
|
...initWebhookData,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
|
||||||
|
});
|
||||||
|
setValidator({ ...initWebhookValidatorData });
|
||||||
|
fetchWebookData();
|
||||||
|
}
|
||||||
|
view === UpdateModalViews.ADD && onClose();
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
isOpen &&
|
||||||
|
view === UpdateModalViews.Edit &&
|
||||||
|
selectedWebhook &&
|
||||||
|
Object.keys(selectedWebhook || {}).length
|
||||||
|
) {
|
||||||
|
const { headers, ...rest } = selectedWebhook;
|
||||||
|
const headerItems = Object.entries(headers || {});
|
||||||
|
if (headerItems.length) {
|
||||||
|
let formattedHeadersData = headerItems.map((headerData) => {
|
||||||
|
return {
|
||||||
|
[WebhookInputHeaderFields.KEY]: headerData[0],
|
||||||
|
[WebhookInputHeaderFields.VALUE]: headerData[1],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setWebhook({
|
||||||
|
...rest,
|
||||||
|
[WebhookInputDataFields.HEADERS]: formattedHeadersData,
|
||||||
|
});
|
||||||
|
setValidator({
|
||||||
|
...validator,
|
||||||
|
[WebhookInputDataFields.HEADERS]: new Array(
|
||||||
|
formattedHeadersData.length
|
||||||
|
)
|
||||||
|
.fill({})
|
||||||
|
.map(() => ({ ...initHeadersValidatorData })),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setWebhook({
|
||||||
|
...rest,
|
||||||
|
[WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
const verifyEndpoint = async () => {
|
||||||
|
if (!validateData()) return;
|
||||||
|
setVerifyingEndpoint(true);
|
||||||
|
const { [WebhookInputDataFields.ENABLED]: _, ...params } = getParams();
|
||||||
|
const res = await client.mutation(TestEndpoint, { params }).toPromise();
|
||||||
|
if (
|
||||||
|
res.data?._test_endpoint?.http_status >= 200 &&
|
||||||
|
res.data?._test_endpoint?.http_status < 400
|
||||||
|
) {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.VERIFIED);
|
||||||
|
} else {
|
||||||
|
setVerifiedStatus(webhookVerifiedStatus.NOT_VERIFIED);
|
||||||
|
}
|
||||||
|
setVerifyingEndpoint(false);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{view === UpdateModalViews.ADD ? (
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onOpen}
|
||||||
|
isDisabled={false}
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Center h="100%">Add Webhook</Center>{' '}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<MenuItem onClick={onOpen}>Edit</MenuItem>
|
||||||
|
)}
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} size="3xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>
|
||||||
|
{view === UpdateModalViews.ADD ? 'Add New Webhook' : 'Edit Webhook'}
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
border="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor="gray.200"
|
||||||
|
p="5"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Event Name</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<Select
|
||||||
|
size="md"
|
||||||
|
value={webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.EVENT_NAME,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{Object.entries(webhookEventNames).map(
|
||||||
|
([key, value]: any) => (
|
||||||
|
<option value={value} key={key}>
|
||||||
|
{key}
|
||||||
|
</option>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="start"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="5%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Endpoint</Flex>
|
||||||
|
<Flex flex="3">
|
||||||
|
<InputGroup size="md">
|
||||||
|
<Input
|
||||||
|
pr="4.5rem"
|
||||||
|
type="text"
|
||||||
|
placeholder="https://domain.com/webhook"
|
||||||
|
value={webhook[WebhookInputDataFields.ENDPOINT]}
|
||||||
|
isInvalid={!validator[WebhookInputDataFields.ENDPOINT]}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.ENDPOINT,
|
||||||
|
e.currentTarget.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="5%"
|
||||||
|
>
|
||||||
|
<Flex flex="1">Enabled</Flex>
|
||||||
|
<Flex w="25%" justifyContent="space-between">
|
||||||
|
<Text h="75%" fontWeight="bold" marginRight="2">
|
||||||
|
Off
|
||||||
|
</Text>
|
||||||
|
<Switch
|
||||||
|
size="md"
|
||||||
|
isChecked={webhook[WebhookInputDataFields.ENABLED]}
|
||||||
|
onChange={() =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.ENABLED,
|
||||||
|
!webhook[WebhookInputDataFields.ENABLED]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Text h="75%" fontWeight="bold" marginLeft="2">
|
||||||
|
On
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="5%"
|
||||||
|
flexDirection="column"
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Flex>
|
||||||
|
Payload
|
||||||
|
<Text color="gray.500" ml={1}>
|
||||||
|
(example)
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
<Button
|
||||||
|
onClick={() => setIsShowingPayload(!isShowingPayload)}
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
{isShowingPayload ? <FaAngleUp /> : <FaAngleDown />}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
<Collapse
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
in={isShowingPayload}
|
||||||
|
>
|
||||||
|
<Code
|
||||||
|
width="inherit"
|
||||||
|
borderRadius={5}
|
||||||
|
padding={2}
|
||||||
|
position="relative"
|
||||||
|
>
|
||||||
|
<pre style={{ overflow: 'auto' }}>
|
||||||
|
{webhookPayloadExample}
|
||||||
|
</pre>
|
||||||
|
{isShowingPayload && (
|
||||||
|
<Flex
|
||||||
|
position="absolute"
|
||||||
|
top={4}
|
||||||
|
right={4}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={() =>
|
||||||
|
copyTextToClipboard(webhookPayloadExample)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegClone color="#bfbfbf" />
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</Code>
|
||||||
|
</Collapse>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
width="100%"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
marginBottom="2%"
|
||||||
|
>
|
||||||
|
<Flex>Headers</Flex>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
leftIcon={<FaPlus />}
|
||||||
|
colorScheme="blue"
|
||||||
|
h="1.75rem"
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
paddingRight="0"
|
||||||
|
onClick={() => updateHeaders(ArrayInputOperations.APPEND)}
|
||||||
|
>
|
||||||
|
Add more Headers
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
<Flex flexDirection="column" maxH={220} overflowY="scroll">
|
||||||
|
{webhook[WebhookInputDataFields.HEADERS]?.map(
|
||||||
|
(headerData, index) => (
|
||||||
|
<Flex
|
||||||
|
key={`header-data-${index}`}
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<InputGroup size="md" marginBottom="2.5%">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="key"
|
||||||
|
value={headerData[WebhookInputHeaderFields.KEY]}
|
||||||
|
isInvalid={
|
||||||
|
!validator[WebhookInputDataFields.HEADERS][index]?.[
|
||||||
|
WebhookInputHeaderFields.KEY
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.HEADERS,
|
||||||
|
e.target.value,
|
||||||
|
WebhookInputHeaderFields.KEY,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
width="30%"
|
||||||
|
marginRight="2%"
|
||||||
|
/>
|
||||||
|
<Center marginRight="2%">
|
||||||
|
<Text fontWeight="bold">:</Text>
|
||||||
|
</Center>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="value"
|
||||||
|
value={headerData[WebhookInputHeaderFields.VALUE]}
|
||||||
|
isInvalid={
|
||||||
|
!validator[WebhookInputDataFields.HEADERS][index]?.[
|
||||||
|
WebhookInputHeaderFields.VALUE
|
||||||
|
]
|
||||||
|
}
|
||||||
|
onChange={(e) =>
|
||||||
|
inputChangehandler(
|
||||||
|
WebhookInputDataFields.HEADERS,
|
||||||
|
e.target.value,
|
||||||
|
WebhookInputHeaderFields.VALUE,
|
||||||
|
index
|
||||||
|
)
|
||||||
|
}
|
||||||
|
width="65%"
|
||||||
|
/>
|
||||||
|
<InputRightElement width="3rem">
|
||||||
|
<Button
|
||||||
|
width="6rem"
|
||||||
|
colorScheme="blackAlpha"
|
||||||
|
variant="ghost"
|
||||||
|
padding="0"
|
||||||
|
onClick={() =>
|
||||||
|
updateHeaders(ArrayInputOperations.REMOVE, index)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaMinusCircle />
|
||||||
|
</Button>
|
||||||
|
</InputRightElement>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
colorScheme={
|
||||||
|
verifiedStatus === webhookVerifiedStatus.VERIFIED
|
||||||
|
? 'green'
|
||||||
|
: verifiedStatus === webhookVerifiedStatus.PENDING
|
||||||
|
? 'yellow'
|
||||||
|
: 'red'
|
||||||
|
}
|
||||||
|
variant="outline"
|
||||||
|
onClick={verifyEndpoint}
|
||||||
|
isLoading={verifyingEndpoint}
|
||||||
|
isDisabled={!validateData()}
|
||||||
|
marginRight="5"
|
||||||
|
leftIcon={
|
||||||
|
verifiedStatus === webhookVerifiedStatus.VERIFIED ? (
|
||||||
|
<BiCheckCircle />
|
||||||
|
) : verifiedStatus === webhookVerifiedStatus.PENDING ? (
|
||||||
|
<BiErrorCircle />
|
||||||
|
) : (
|
||||||
|
<BiError />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{verifiedStatus === webhookVerifiedStatus.VERIFIED
|
||||||
|
? 'Endpoint Verified'
|
||||||
|
: verifiedStatus === webhookVerifiedStatus.PENDING
|
||||||
|
? 'Test Endpoint'
|
||||||
|
: 'Endpoint Not Verified'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={saveData}
|
||||||
|
isDisabled={!validateData()}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Save
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UpdateWebhookModal;
|
426
dashboard/src/components/ViewWebhookLogsModal.tsx
Normal file
426
dashboard/src/components/ViewWebhookLogsModal.tsx
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
MenuItem,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalCloseButton,
|
||||||
|
ModalContent,
|
||||||
|
ModalFooter,
|
||||||
|
ModalHeader,
|
||||||
|
ModalOverlay,
|
||||||
|
useDisclosure,
|
||||||
|
Text,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
Tbody,
|
||||||
|
IconButton,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
TableCaption,
|
||||||
|
Tooltip,
|
||||||
|
Td,
|
||||||
|
Tag,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
FaAngleDoubleLeft,
|
||||||
|
FaAngleDoubleRight,
|
||||||
|
FaAngleLeft,
|
||||||
|
FaAngleRight,
|
||||||
|
FaExclamationCircle,
|
||||||
|
FaRegClone,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import { copyTextToClipboard } from '../utils';
|
||||||
|
import { WebhookLogsQuery } from '../graphql/queries';
|
||||||
|
import { pageLimits } from '../constants';
|
||||||
|
|
||||||
|
interface paginationPropTypes {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
offset: number;
|
||||||
|
total: number;
|
||||||
|
maxPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface deleteWebhookModalInputPropTypes {
|
||||||
|
webhookId: string;
|
||||||
|
eventName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface webhookLogsDataTypes {
|
||||||
|
id: string;
|
||||||
|
http_status: number;
|
||||||
|
request: string;
|
||||||
|
response: string;
|
||||||
|
created_at: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ViewWebhookLogsModal = ({
|
||||||
|
webhookId,
|
||||||
|
eventName,
|
||||||
|
}: deleteWebhookModalInputPropTypes) => {
|
||||||
|
const client = useClient();
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [webhookLogs, setWebhookLogs] = useState<webhookLogsDataTypes[]>([]);
|
||||||
|
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
|
||||||
|
limit: 5,
|
||||||
|
page: 1,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
maxPages: 1,
|
||||||
|
});
|
||||||
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
const { limit, total } = pagination;
|
||||||
|
if (total > 1) {
|
||||||
|
return total % limit === 0
|
||||||
|
? total / limit
|
||||||
|
: parseInt(`${total / limit}`) + 1;
|
||||||
|
} else return 1;
|
||||||
|
};
|
||||||
|
const fetchWebhookLogsData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await client
|
||||||
|
.query(WebhookLogsQuery, {
|
||||||
|
params: {
|
||||||
|
webhook_id: webhookId,
|
||||||
|
pagination: {
|
||||||
|
limit: paginationProps.limit,
|
||||||
|
page: paginationProps.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.data?._webhook_logs) {
|
||||||
|
const { pagination, webhook_logs } = res.data?._webhook_logs;
|
||||||
|
const maxPages = getMaxPages(pagination);
|
||||||
|
if (webhook_logs?.length) {
|
||||||
|
setWebhookLogs(webhook_logs);
|
||||||
|
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||||
|
} else {
|
||||||
|
if (paginationProps.page !== 1) {
|
||||||
|
setPaginationProps({
|
||||||
|
...paginationProps,
|
||||||
|
...pagination,
|
||||||
|
maxPages,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const paginationHandler = (value: Record<string, number>) => {
|
||||||
|
setPaginationProps({ ...paginationProps, ...value });
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
isOpen && fetchWebhookLogsData();
|
||||||
|
}, [isOpen, paginationProps.page, paginationProps.limit]);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MenuItem onClick={onOpen}>View Logs</MenuItem>
|
||||||
|
<Modal isOpen={isOpen} onClose={onClose} size="4xl">
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalHeader>Webhook Logs - {eventName}</ModalHeader>
|
||||||
|
<ModalCloseButton />
|
||||||
|
<ModalBody>
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
border="1px"
|
||||||
|
borderRadius="md"
|
||||||
|
borderColor="gray.200"
|
||||||
|
p="5"
|
||||||
|
>
|
||||||
|
{!loading ? (
|
||||||
|
webhookLogs.length ? (
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>ID</Th>
|
||||||
|
<Th>Created At</Th>
|
||||||
|
<Th>Http Status</Th>
|
||||||
|
<Th>Request</Th>
|
||||||
|
<Th>Response</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{webhookLogs.map((logData: webhookLogsDataTypes) => (
|
||||||
|
<Tr key={logData.id} style={{ fontSize: 14 }}>
|
||||||
|
<Td>
|
||||||
|
<Text fontSize="sm">{`${logData.id.substring(
|
||||||
|
0,
|
||||||
|
5
|
||||||
|
)}***${logData.id.substring(
|
||||||
|
logData.id.length - 5,
|
||||||
|
logData.id.length
|
||||||
|
)}`}</Text>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{dayjs(logData.created_at * 1000).format(
|
||||||
|
'MMM DD, YYYY'
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
logData.http_status >= 400 ? 'red' : 'green'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{logData.http_status}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Tooltip
|
||||||
|
bg="gray.300"
|
||||||
|
color="black"
|
||||||
|
label={logData.request || 'null'}
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
logData.request ? 'gray' : 'yellow'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{logData.request ? 'Payload' : 'No Data'}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
{logData.request && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
marginLeft="5px"
|
||||||
|
h="21px"
|
||||||
|
onClick={() =>
|
||||||
|
copyTextToClipboard(logData.request)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegClone color="#bfbfbf" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Tooltip
|
||||||
|
bg="gray.300"
|
||||||
|
color="black"
|
||||||
|
label={logData.response || 'null'}
|
||||||
|
>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
logData.response ? 'gray' : 'yellow'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{logData.response ? 'Preview' : 'No Data'}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
{logData.response && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
marginLeft="5px"
|
||||||
|
h="21px"
|
||||||
|
onClick={() =>
|
||||||
|
copyTextToClipboard(logData.response)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<FaRegClone color="#bfbfbf" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 ||
|
||||||
|
paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageLimits.map((pageSize) => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >=
|
||||||
|
paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >=
|
||||||
|
paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TableCaption>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
minH="25vh"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center w="50px" marginRight="1.5%">
|
||||||
|
<FaExclamationCircle
|
||||||
|
style={{ color: '#f0f0f0', fontSize: 70 }}
|
||||||
|
/>
|
||||||
|
</Center>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
paddingRight="1%"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#d9d9d9"
|
||||||
|
>
|
||||||
|
No Data
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="solid"
|
||||||
|
onClick={onClose}
|
||||||
|
isDisabled={false}
|
||||||
|
>
|
||||||
|
<Center h="100%" pt="5%">
|
||||||
|
Close
|
||||||
|
</Center>
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ViewWebhookLogsModal;
|
@@ -153,3 +153,105 @@ export const envSubViews = {
|
|||||||
ADMIN_SECRET: 'admin-secret',
|
ADMIN_SECRET: 'admin-secret',
|
||||||
DB_CRED: 'db-cred',
|
DB_CRED: 'db-cred',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum WebhookInputDataFields {
|
||||||
|
ID = 'id',
|
||||||
|
EVENT_NAME = 'event_name',
|
||||||
|
ENDPOINT = 'endpoint',
|
||||||
|
ENABLED = 'enabled',
|
||||||
|
HEADERS = 'headers',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EmailTemplateInputDataFields {
|
||||||
|
ID = 'id',
|
||||||
|
EVENT_NAME = 'event_name',
|
||||||
|
SUBJECT = 'subject',
|
||||||
|
CREATED_AT = 'created_at',
|
||||||
|
TEMPLATE = 'template',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum WebhookInputHeaderFields {
|
||||||
|
KEY = 'key',
|
||||||
|
VALUE = 'value',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum UpdateModalViews {
|
||||||
|
ADD = 'add',
|
||||||
|
Edit = 'edit',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pageLimits: number[] = [5, 10, 15];
|
||||||
|
|
||||||
|
export const webhookEventNames = {
|
||||||
|
USER_SIGNUP: 'user.signup',
|
||||||
|
USER_CREATED: 'user.created',
|
||||||
|
USER_LOGIN: 'user.login',
|
||||||
|
USER_DELETED: 'user.deleted',
|
||||||
|
USER_ACCESS_ENABLED: 'user.access_enabled',
|
||||||
|
USER_ACCESS_REVOKED: 'user.access_revoked',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emailTemplateEventNames = {
|
||||||
|
BASIC_AUTH_SIGNUP: 'basic_auth_signup',
|
||||||
|
MAGIC_LINK_LOGIN: 'magic_link_login',
|
||||||
|
UPDATE_EMAIL: 'update_email',
|
||||||
|
FORGOT_PASSWORD: 'forgot_password',
|
||||||
|
VERIFY_OTP: 'verify_otp',
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum webhookVerifiedStatus {
|
||||||
|
VERIFIED = 'verified',
|
||||||
|
NOT_VERIFIED = 'not_verified',
|
||||||
|
PENDING = 'verification_pending',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const emailTemplateVariables = {
|
||||||
|
'user.id': '{user.id}}',
|
||||||
|
'user.email': '{user.email}}',
|
||||||
|
'user.given_name': '{user.given_name}}',
|
||||||
|
'user.family_name': '{user.family_name}}',
|
||||||
|
'user.signup_methods': '{user.signup_methods}}',
|
||||||
|
'user.email_verified': '{user.email_verified}}',
|
||||||
|
'user.picture': '{user.picture}}',
|
||||||
|
'user.roles': '{user.roles}}',
|
||||||
|
'user.middle_name': '{user.middle_name}}',
|
||||||
|
'user.nickname': '{user.nickname}}',
|
||||||
|
'user.preferred_username': '{user.preferred_username}}',
|
||||||
|
'user.gender': '{user.gender}}',
|
||||||
|
'user.birthdate': '{user.birthdate}}',
|
||||||
|
'user.phone_number': '{user.phone_number}}',
|
||||||
|
'user.phone_number_verified': '{user.phone_number_verified}}',
|
||||||
|
'user.created_at': '{user.created_at}}',
|
||||||
|
'user.updated_at': '{user.updated_at}}',
|
||||||
|
'organization.name': '{organization.name}}',
|
||||||
|
'organization.logo': '{organization.logo}}',
|
||||||
|
verification_url: '{verification_url}}',
|
||||||
|
otp: '{otp}}',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const webhookPayloadExample: string = `{
|
||||||
|
"event_name":"user.login",
|
||||||
|
"user":{
|
||||||
|
"birthdate":null,
|
||||||
|
"created_at":1657524721,
|
||||||
|
"email":"lakhan.m.samani@gmail.com",
|
||||||
|
"email_verified":true,
|
||||||
|
"family_name":"Samani",
|
||||||
|
"gender":null,
|
||||||
|
"given_name":"Lakhan",
|
||||||
|
"id":"466d0b31-1b87-420e-bea5-09d05d79c586",
|
||||||
|
"middle_name":null,
|
||||||
|
"nickname":null,
|
||||||
|
"phone_number":null,
|
||||||
|
"phone_number_verified":false,
|
||||||
|
"picture":"https://lh3.googleusercontent.com/a-/AFdZucppvU6a2zIDkX0wvhhapVjT0ZMKDlYCkQDi3NxcUg=s96-c",
|
||||||
|
"preferred_username":"lakhan.m.samani@gmail.com",
|
||||||
|
"revoked_timestamp":null,
|
||||||
|
"roles":[
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"signup_methods":"google",
|
||||||
|
"updated_at":1657526492
|
||||||
|
},
|
||||||
|
"auth_recipe":"google"
|
||||||
|
}`;
|
||||||
|
@@ -79,3 +79,60 @@ export const GenerateKeys = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const AddWebhook = `
|
||||||
|
mutation addWebhook($params: AddWebhookRequest!) {
|
||||||
|
_add_webhook(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EditWebhook = `
|
||||||
|
mutation editWebhook($params: UpdateWebhookRequest!) {
|
||||||
|
_update_webhook(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DeleteWebhook = `
|
||||||
|
mutation deleteWebhook($params: WebhookRequest!) {
|
||||||
|
_delete_webhook(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TestEndpoint = `
|
||||||
|
mutation testEndpoint($params: TestEndpointRequest!) {
|
||||||
|
_test_endpoint(params: $params) {
|
||||||
|
http_status
|
||||||
|
response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AddEmailTemplate = `
|
||||||
|
mutation addEmailTemplate($params: AddEmailTemplateRequest!) {
|
||||||
|
_add_email_template(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EditEmailTemplate = `
|
||||||
|
mutation editEmailTemplate($params: UpdateEmailTemplateRequest!) {
|
||||||
|
_update_email_template(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DeleteEmailTemplate = `
|
||||||
|
mutation deleteEmailTemplate($params: DeleteEmailTemplateRequest!) {
|
||||||
|
_delete_email_template(params: $params) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@@ -101,3 +101,63 @@ export const EmailVerificationQuery = `
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const WebhooksDataQuery = `
|
||||||
|
query getWebhooksData($params: PaginatedInput!) {
|
||||||
|
_webhooks(params: $params){
|
||||||
|
webhooks{
|
||||||
|
id
|
||||||
|
event_name
|
||||||
|
endpoint
|
||||||
|
enabled
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
pagination{
|
||||||
|
limit
|
||||||
|
page
|
||||||
|
offset
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EmailTemplatesQuery = `
|
||||||
|
query getEmailTemplates($params: PaginatedInput!) {
|
||||||
|
_email_templates(params: $params) {
|
||||||
|
EmailTemplates {
|
||||||
|
id
|
||||||
|
event_name
|
||||||
|
subject
|
||||||
|
created_at
|
||||||
|
template
|
||||||
|
}
|
||||||
|
pagination {
|
||||||
|
limit
|
||||||
|
page
|
||||||
|
offset
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const WebhookLogsQuery = `
|
||||||
|
query getWebhookLogs($params: ListWebhookLogRequest!) {
|
||||||
|
_webhook_logs(params: $params) {
|
||||||
|
webhook_logs {
|
||||||
|
id
|
||||||
|
http_status
|
||||||
|
request
|
||||||
|
response
|
||||||
|
created_at
|
||||||
|
}
|
||||||
|
pagination {
|
||||||
|
limit
|
||||||
|
page
|
||||||
|
offset
|
||||||
|
total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
347
dashboard/src/pages/EmailTemplates.tsx
Normal file
347
dashboard/src/pages/EmailTemplates.tsx
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
TableCaption,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Text,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tooltip,
|
||||||
|
Tr,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaAngleDoubleLeft,
|
||||||
|
FaAngleDoubleRight,
|
||||||
|
FaAngleDown,
|
||||||
|
FaAngleLeft,
|
||||||
|
FaAngleRight,
|
||||||
|
FaExclamationCircle,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import UpdateEmailTemplateModal from '../components/UpdateEmailTemplateModal';
|
||||||
|
import {
|
||||||
|
pageLimits,
|
||||||
|
UpdateModalViews,
|
||||||
|
EmailTemplateInputDataFields,
|
||||||
|
} from '../constants';
|
||||||
|
import { EmailTemplatesQuery, WebhooksDataQuery } from '../graphql/queries';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import DeleteEmailTemplateModal from '../components/DeleteEmailTemplateModal';
|
||||||
|
|
||||||
|
interface paginationPropTypes {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
offset: number;
|
||||||
|
total: number;
|
||||||
|
maxPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmailTemplateDataType {
|
||||||
|
[EmailTemplateInputDataFields.ID]: string;
|
||||||
|
[EmailTemplateInputDataFields.EVENT_NAME]: string;
|
||||||
|
[EmailTemplateInputDataFields.SUBJECT]: string;
|
||||||
|
[EmailTemplateInputDataFields.CREATED_AT]: number;
|
||||||
|
[EmailTemplateInputDataFields.TEMPLATE]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmailTemplates = () => {
|
||||||
|
const client = useClient();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [emailTemplatesData, setEmailTemplatesData] = useState<
|
||||||
|
EmailTemplateDataType[]
|
||||||
|
>([]);
|
||||||
|
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
|
||||||
|
limit: 5,
|
||||||
|
page: 1,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
maxPages: 1,
|
||||||
|
});
|
||||||
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
const { limit, total } = pagination;
|
||||||
|
if (total > 1) {
|
||||||
|
return total % limit === 0
|
||||||
|
? total / limit
|
||||||
|
: parseInt(`${total / limit}`) + 1;
|
||||||
|
} else return 1;
|
||||||
|
};
|
||||||
|
const fetchEmailTemplatesData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await client
|
||||||
|
.query(EmailTemplatesQuery, {
|
||||||
|
params: {
|
||||||
|
pagination: {
|
||||||
|
limit: paginationProps.limit,
|
||||||
|
page: paginationProps.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.data?._email_templates) {
|
||||||
|
const { pagination, EmailTemplates: emailTemplates } =
|
||||||
|
res.data?._email_templates;
|
||||||
|
const maxPages = getMaxPages(pagination);
|
||||||
|
if (emailTemplates?.length) {
|
||||||
|
setEmailTemplatesData(emailTemplates);
|
||||||
|
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||||
|
} else {
|
||||||
|
if (paginationProps.page !== 1) {
|
||||||
|
setPaginationProps({
|
||||||
|
...paginationProps,
|
||||||
|
...pagination,
|
||||||
|
maxPages,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const paginationHandler = (value: Record<string, number>) => {
|
||||||
|
setPaginationProps({ ...paginationProps, ...value });
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchEmailTemplatesData();
|
||||||
|
}, [paginationProps.page, paginationProps.limit]);
|
||||||
|
return (
|
||||||
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
|
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||||
|
<Text fontSize="md" fontWeight="bold">
|
||||||
|
Email Templates
|
||||||
|
</Text>
|
||||||
|
<UpdateEmailTemplateModal
|
||||||
|
view={UpdateModalViews.ADD}
|
||||||
|
fetchEmailTemplatesData={fetchEmailTemplatesData}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{!loading ? (
|
||||||
|
emailTemplatesData.length ? (
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Event Name</Th>
|
||||||
|
<Th>Subject</Th>
|
||||||
|
<Th>Created At</Th>
|
||||||
|
<Th>Actions</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{emailTemplatesData.map((templateData: EmailTemplateDataType) => (
|
||||||
|
<Tr
|
||||||
|
key={templateData[EmailTemplateInputDataFields.ID]}
|
||||||
|
style={{ fontSize: 14 }}
|
||||||
|
>
|
||||||
|
<Td maxW="300">
|
||||||
|
{templateData[EmailTemplateInputDataFields.EVENT_NAME]}
|
||||||
|
</Td>
|
||||||
|
<Td>{templateData[EmailTemplateInputDataFields.SUBJECT]}</Td>
|
||||||
|
<Td>
|
||||||
|
{dayjs(templateData.created_at * 1000).format(
|
||||||
|
'MMM DD, YYYY'
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm" fontWeight="light">
|
||||||
|
Menu
|
||||||
|
</Text>
|
||||||
|
<FaAngleDown style={{ marginLeft: 10 }} />
|
||||||
|
</Flex>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<UpdateEmailTemplateModal
|
||||||
|
view={UpdateModalViews.Edit}
|
||||||
|
selectedTemplate={templateData}
|
||||||
|
fetchEmailTemplatesData={fetchEmailTemplatesData}
|
||||||
|
/>
|
||||||
|
<DeleteEmailTemplateModal
|
||||||
|
emailTemplateId={
|
||||||
|
templateData[EmailTemplateInputDataFields.ID]
|
||||||
|
}
|
||||||
|
eventName={
|
||||||
|
templateData[
|
||||||
|
EmailTemplateInputDataFields.EVENT_NAME
|
||||||
|
]
|
||||||
|
}
|
||||||
|
fetchEmailTemplatesData={fetchEmailTemplatesData}
|
||||||
|
/>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageLimits.map((pageSize) => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TableCaption>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
minH="25vh"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center w="50px" marginRight="1.5%">
|
||||||
|
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
|
||||||
|
</Center>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
paddingRight="1%"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#d9d9d9"
|
||||||
|
>
|
||||||
|
No Data
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmailTemplates;
|
369
dashboard/src/pages/Webhooks.tsx
Normal file
369
dashboard/src/pages/Webhooks.tsx
Normal file
@@ -0,0 +1,369 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useClient } from 'urql';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
NumberDecrementStepper,
|
||||||
|
NumberIncrementStepper,
|
||||||
|
NumberInput,
|
||||||
|
NumberInputField,
|
||||||
|
NumberInputStepper,
|
||||||
|
Select,
|
||||||
|
Spinner,
|
||||||
|
Table,
|
||||||
|
TableCaption,
|
||||||
|
Tag,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Text,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tooltip,
|
||||||
|
Tr,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
FaAngleDoubleLeft,
|
||||||
|
FaAngleDoubleRight,
|
||||||
|
FaAngleDown,
|
||||||
|
FaAngleLeft,
|
||||||
|
FaAngleRight,
|
||||||
|
FaExclamationCircle,
|
||||||
|
} from 'react-icons/fa';
|
||||||
|
import UpdateWebhookModal from '../components/UpdateWebhookModal';
|
||||||
|
import {
|
||||||
|
pageLimits,
|
||||||
|
WebhookInputDataFields,
|
||||||
|
UpdateModalViews,
|
||||||
|
} from '../constants';
|
||||||
|
import { WebhooksDataQuery } from '../graphql/queries';
|
||||||
|
import DeleteWebhookModal from '../components/DeleteWebhookModal';
|
||||||
|
import ViewWebhookLogsModal from '../components/ViewWebhookLogsModal';
|
||||||
|
|
||||||
|
interface paginationPropTypes {
|
||||||
|
limit: number;
|
||||||
|
page: number;
|
||||||
|
offset: number;
|
||||||
|
total: number;
|
||||||
|
maxPages: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface webhookDataTypes {
|
||||||
|
[WebhookInputDataFields.ID]: string;
|
||||||
|
[WebhookInputDataFields.EVENT_NAME]: string;
|
||||||
|
[WebhookInputDataFields.ENDPOINT]: string;
|
||||||
|
[WebhookInputDataFields.ENABLED]: boolean;
|
||||||
|
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Webhooks = () => {
|
||||||
|
const client = useClient();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [webhookData, setWebhookData] = useState<webhookDataTypes[]>([]);
|
||||||
|
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
|
||||||
|
limit: 5,
|
||||||
|
page: 1,
|
||||||
|
offset: 0,
|
||||||
|
total: 0,
|
||||||
|
maxPages: 1,
|
||||||
|
});
|
||||||
|
const getMaxPages = (pagination: paginationPropTypes) => {
|
||||||
|
const { limit, total } = pagination;
|
||||||
|
if (total > 1) {
|
||||||
|
return total % limit === 0
|
||||||
|
? total / limit
|
||||||
|
: parseInt(`${total / limit}`) + 1;
|
||||||
|
} else return 1;
|
||||||
|
};
|
||||||
|
const fetchWebookData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const res = await client
|
||||||
|
.query(WebhooksDataQuery, {
|
||||||
|
params: {
|
||||||
|
pagination: {
|
||||||
|
limit: paginationProps.limit,
|
||||||
|
page: paginationProps.page,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.toPromise();
|
||||||
|
if (res.data?._webhooks) {
|
||||||
|
const { pagination, webhooks } = res.data?._webhooks;
|
||||||
|
const maxPages = getMaxPages(pagination);
|
||||||
|
if (webhooks?.length) {
|
||||||
|
setWebhookData(webhooks);
|
||||||
|
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
|
||||||
|
} else {
|
||||||
|
if (paginationProps.page !== 1) {
|
||||||
|
setPaginationProps({
|
||||||
|
...paginationProps,
|
||||||
|
...pagination,
|
||||||
|
maxPages,
|
||||||
|
page: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
const paginationHandler = (value: Record<string, number>) => {
|
||||||
|
setPaginationProps({ ...paginationProps, ...value });
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchWebookData();
|
||||||
|
}, [paginationProps.page, paginationProps.limit]);
|
||||||
|
return (
|
||||||
|
<Box m="5" py="5" px="10" bg="white" rounded="md">
|
||||||
|
<Flex margin="2% 0" justifyContent="space-between" alignItems="center">
|
||||||
|
<Text fontSize="md" fontWeight="bold">
|
||||||
|
Webhooks
|
||||||
|
</Text>
|
||||||
|
<UpdateWebhookModal
|
||||||
|
view={UpdateModalViews.ADD}
|
||||||
|
fetchWebookData={fetchWebookData}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
{!loading ? (
|
||||||
|
webhookData.length ? (
|
||||||
|
<Table variant="simple">
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Event Name</Th>
|
||||||
|
<Th>Endpoint</Th>
|
||||||
|
<Th>Enabled</Th>
|
||||||
|
<Th>Headers</Th>
|
||||||
|
<Th>Actions</Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{webhookData.map((webhook: webhookDataTypes) => (
|
||||||
|
<Tr
|
||||||
|
key={webhook[WebhookInputDataFields.ID]}
|
||||||
|
style={{ fontSize: 14 }}
|
||||||
|
>
|
||||||
|
<Td maxW="300">
|
||||||
|
{webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
</Td>
|
||||||
|
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
|
||||||
|
<Td>
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
colorScheme={
|
||||||
|
webhook[WebhookInputDataFields.ENABLED]
|
||||||
|
? 'green'
|
||||||
|
: 'yellow'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{webhook[WebhookInputDataFields.ENABLED].toString()}
|
||||||
|
</Tag>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Tooltip
|
||||||
|
bg="gray.300"
|
||||||
|
color="black"
|
||||||
|
label={JSON.stringify(
|
||||||
|
webhook[WebhookInputDataFields.HEADERS],
|
||||||
|
null,
|
||||||
|
' '
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Tag size="sm" variant="outline" colorScheme="gray">
|
||||||
|
{Object.keys(
|
||||||
|
webhook[WebhookInputDataFields.HEADERS] || {}
|
||||||
|
)?.length.toString()}
|
||||||
|
</Tag>
|
||||||
|
</Tooltip>
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Menu>
|
||||||
|
<MenuButton as={Button} variant="unstyled" size="sm">
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="sm" fontWeight="light">
|
||||||
|
Menu
|
||||||
|
</Text>
|
||||||
|
<FaAngleDown style={{ marginLeft: 10 }} />
|
||||||
|
</Flex>
|
||||||
|
</MenuButton>
|
||||||
|
<MenuList>
|
||||||
|
<UpdateWebhookModal
|
||||||
|
view={UpdateModalViews.Edit}
|
||||||
|
selectedWebhook={webhook}
|
||||||
|
fetchWebookData={fetchWebookData}
|
||||||
|
/>
|
||||||
|
<DeleteWebhookModal
|
||||||
|
webhookId={webhook[WebhookInputDataFields.ID]}
|
||||||
|
eventName={webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
fetchWebookData={fetchWebookData}
|
||||||
|
/>
|
||||||
|
<ViewWebhookLogsModal
|
||||||
|
webhookId={webhook[WebhookInputDataFields.ID]}
|
||||||
|
eventName={webhook[WebhookInputDataFields.EVENT_NAME]}
|
||||||
|
/>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
))}
|
||||||
|
</Tbody>
|
||||||
|
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
|
||||||
|
<TableCaption>
|
||||||
|
<Flex
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
m="2% 0"
|
||||||
|
>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="First Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
mr={4}
|
||||||
|
icon={<FaAngleDoubleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Previous Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page - 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={paginationProps.page <= 1}
|
||||||
|
icon={<FaAngleLeft />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
<Flex
|
||||||
|
flex="8"
|
||||||
|
justifyContent="space-evenly"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Text mr={8}>
|
||||||
|
Page{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.page}
|
||||||
|
</Text>{' '}
|
||||||
|
of{' '}
|
||||||
|
<Text fontWeight="bold" as="span">
|
||||||
|
{paginationProps.maxPages}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Flex alignItems="center">
|
||||||
|
<Text flexShrink="0">Go to page:</Text>{' '}
|
||||||
|
<NumberInput
|
||||||
|
ml={2}
|
||||||
|
mr={8}
|
||||||
|
w={28}
|
||||||
|
min={1}
|
||||||
|
max={paginationProps.maxPages}
|
||||||
|
onChange={(value) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: parseInt(value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={paginationProps.page}
|
||||||
|
>
|
||||||
|
<NumberInputField />
|
||||||
|
<NumberInputStepper>
|
||||||
|
<NumberIncrementStepper />
|
||||||
|
<NumberDecrementStepper />
|
||||||
|
</NumberInputStepper>
|
||||||
|
</NumberInput>
|
||||||
|
</Flex>
|
||||||
|
<Select
|
||||||
|
w={32}
|
||||||
|
value={paginationProps.limit}
|
||||||
|
onChange={(e) =>
|
||||||
|
paginationHandler({
|
||||||
|
page: 1,
|
||||||
|
limit: parseInt(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageLimits.map((pageSize) => (
|
||||||
|
<option key={pageSize} value={pageSize}>
|
||||||
|
Show {pageSize}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Flex>
|
||||||
|
<Flex flex="1">
|
||||||
|
<Tooltip label="Next Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.page + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
icon={<FaAngleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip label="Last Page">
|
||||||
|
<IconButton
|
||||||
|
aria-label="icon button"
|
||||||
|
onClick={() =>
|
||||||
|
paginationHandler({
|
||||||
|
page: paginationProps.maxPages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
isDisabled={
|
||||||
|
paginationProps.page >= paginationProps.maxPages
|
||||||
|
}
|
||||||
|
ml={4}
|
||||||
|
icon={<FaAngleDoubleRight />}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</TableCaption>
|
||||||
|
)}
|
||||||
|
</Table>
|
||||||
|
) : (
|
||||||
|
<Flex
|
||||||
|
flexDirection="column"
|
||||||
|
minH="25vh"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Center w="50px" marginRight="1.5%">
|
||||||
|
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
|
||||||
|
</Center>
|
||||||
|
<Text
|
||||||
|
fontSize="2xl"
|
||||||
|
paddingRight="1%"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="#d9d9d9"
|
||||||
|
>
|
||||||
|
No Data
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Center minH="25vh">
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Webhooks;
|
@@ -3,37 +3,41 @@ import { Outlet, Route, Routes } from 'react-router-dom';
|
|||||||
|
|
||||||
import { useAuthContext } from '../contexts/AuthContext';
|
import { useAuthContext } from '../contexts/AuthContext';
|
||||||
import { DashboardLayout } from '../layouts/DashboardLayout';
|
import { DashboardLayout } from '../layouts/DashboardLayout';
|
||||||
|
import EmailTemplates from '../pages/EmailTemplates';
|
||||||
|
|
||||||
const Auth = lazy(() => import('../pages/Auth'));
|
const Auth = lazy(() => import('../pages/Auth'));
|
||||||
const Environment = lazy(() => import('../pages/Environment'));
|
const Environment = lazy(() => import('../pages/Environment'));
|
||||||
const Home = lazy(() => import('../pages/Home'));
|
const Home = lazy(() => import('../pages/Home'));
|
||||||
const Users = lazy(() => import('../pages/Users'));
|
const Users = lazy(() => import('../pages/Users'));
|
||||||
|
const Webhooks = lazy(() => import('../pages/Webhooks'));
|
||||||
|
|
||||||
export const AppRoutes = () => {
|
export const AppRoutes = () => {
|
||||||
const { isLoggedIn } = useAuthContext();
|
const { isLoggedIn } = useAuthContext();
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Suspense fallback={<></>}>
|
<Suspense fallback={<></>}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
element={
|
element={
|
||||||
<DashboardLayout>
|
<DashboardLayout>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</DashboardLayout>
|
</DashboardLayout>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Route path="/" element={<Outlet />}>
|
<Route path="/" element={<Outlet />}>
|
||||||
<Route index element={<Environment />} />
|
<Route index element={<Environment />} />
|
||||||
<Route path="/:sec" element={<Environment />} />
|
<Route path="/:sec" element={<Environment />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="users" element={<Users />} />
|
<Route path="users" element={<Users />} />
|
||||||
<Route path="*" element={<Home />} />
|
<Route path="webhooks" element={<Webhooks />} />
|
||||||
</Route>
|
<Route path="email-templates" element={<EmailTemplates />} />
|
||||||
</Routes>
|
<Route path="*" element={<Home />} />
|
||||||
</Suspense>
|
</Route>
|
||||||
</div>
|
</Routes>
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@@ -23,4 +23,6 @@ const (
|
|||||||
DbTypeScyllaDB = "scylladb"
|
DbTypeScyllaDB = "scylladb"
|
||||||
// DbTypeCockroachDB is the cockroach database type
|
// DbTypeCockroachDB is the cockroach database type
|
||||||
DbTypeCockroachDB = "cockroachdb"
|
DbTypeCockroachDB = "cockroachdb"
|
||||||
|
// DbTypePlanetScaleDB is the planetscale database type
|
||||||
|
DbTypePlanetScaleDB = "planetscale"
|
||||||
)
|
)
|
||||||
|
@@ -8,6 +8,9 @@ const (
|
|||||||
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
|
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
|
||||||
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
|
// Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
|
||||||
GithubUserInfoURL = "https://api.github.com/user"
|
GithubUserInfoURL = "https://api.github.com/user"
|
||||||
|
// Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123
|
||||||
|
GithubUserEmails = "https://api/github.com/user/emails"
|
||||||
|
|
||||||
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
|
// Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
|
||||||
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
|
LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
|
||||||
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
|
LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
|
||||||
|
35
server/db/models/email_templates.go
Normal file
35
server/db/models/email_templates.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmailTemplate model for database
|
||||||
|
type EmailTemplate struct {
|
||||||
|
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||||
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
|
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name"`
|
||||||
|
Subject string `gorm:"type:text" json:"subject" bson:"subject" cql:"subject"`
|
||||||
|
Template string `gorm:"type:text" json:"template" bson:"template" cql:"template"`
|
||||||
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsAPIEmailTemplate to return email template as graphql response object
|
||||||
|
func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
|
||||||
|
id := e.ID
|
||||||
|
if strings.Contains(id, Collections.EmailTemplate+"/") {
|
||||||
|
id = strings.TrimPrefix(id, Collections.EmailTemplate+"/")
|
||||||
|
}
|
||||||
|
return &model.EmailTemplate{
|
||||||
|
ID: id,
|
||||||
|
EventName: e.EventName,
|
||||||
|
Subject: e.Subject,
|
||||||
|
Template: e.Template,
|
||||||
|
CreatedAt: refs.NewInt64Ref(e.CreatedAt),
|
||||||
|
UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
|
||||||
|
}
|
||||||
|
}
|
@@ -8,6 +8,7 @@ type CollectionList struct {
|
|||||||
Env string
|
Env string
|
||||||
Webhook string
|
Webhook string
|
||||||
WebhookLog string
|
WebhookLog string
|
||||||
|
EmailTemplate string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -19,7 +20,8 @@ var (
|
|||||||
VerificationRequest: Prefix + "verification_requests",
|
VerificationRequest: Prefix + "verification_requests",
|
||||||
Session: Prefix + "sessions",
|
Session: Prefix + "sessions",
|
||||||
Env: Prefix + "env",
|
Env: Prefix + "env",
|
||||||
Webhook: Prefix + "webhook",
|
Webhook: Prefix + "webhooks",
|
||||||
WebhookLog: Prefix + "webhook_log",
|
WebhookLog: Prefix + "webhook_logs",
|
||||||
|
EmailTemplate: Prefix + "email_templates",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@@ -6,8 +6,7 @@ package models
|
|||||||
type Session struct {
|
type Session struct {
|
||||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id" cql:"user_id"`
|
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id"`
|
||||||
User User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-" cql:"-"`
|
|
||||||
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
|
UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent"`
|
||||||
IP string `json:"ip" bson:"ip" cql:"ip"`
|
IP string `json:"ip" bson:"ip" cql:"ip"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
@@ -35,11 +36,13 @@ type User struct {
|
|||||||
func (user *User) AsAPIUser() *model.User {
|
func (user *User) AsAPIUser() *model.User {
|
||||||
isEmailVerified := user.EmailVerifiedAt != nil
|
isEmailVerified := user.EmailVerifiedAt != nil
|
||||||
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
|
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
|
||||||
email := user.Email
|
|
||||||
createdAt := user.CreatedAt
|
id := user.ID
|
||||||
updatedAt := user.UpdatedAt
|
if strings.Contains(id, Collections.WebhookLog+"/") {
|
||||||
|
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
|
||||||
|
}
|
||||||
return &model.User{
|
return &model.User{
|
||||||
ID: user.ID,
|
ID: id,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
EmailVerified: isEmailVerified,
|
EmailVerified: isEmailVerified,
|
||||||
SignupMethods: user.SignupMethods,
|
SignupMethods: user.SignupMethods,
|
||||||
@@ -47,7 +50,7 @@ func (user *User) AsAPIUser() *model.User {
|
|||||||
FamilyName: user.FamilyName,
|
FamilyName: user.FamilyName,
|
||||||
MiddleName: user.MiddleName,
|
MiddleName: user.MiddleName,
|
||||||
Nickname: user.Nickname,
|
Nickname: user.Nickname,
|
||||||
PreferredUsername: &email,
|
PreferredUsername: refs.NewStringRef(user.Email),
|
||||||
Gender: user.Gender,
|
Gender: user.Gender,
|
||||||
Birthdate: user.Birthdate,
|
Birthdate: user.Birthdate,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber,
|
||||||
@@ -55,7 +58,7 @@ func (user *User) AsAPIUser() *model.User {
|
|||||||
Picture: user.Picture,
|
Picture: user.Picture,
|
||||||
Roles: strings.Split(user.Roles, ","),
|
Roles: strings.Split(user.Roles, ","),
|
||||||
RevokedTimestamp: user.RevokedTimestamp,
|
RevokedTimestamp: user.RevokedTimestamp,
|
||||||
CreatedAt: &createdAt,
|
CreatedAt: refs.NewInt64Ref(user.CreatedAt),
|
||||||
UpdatedAt: &updatedAt,
|
UpdatedAt: refs.NewInt64Ref(user.UpdatedAt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,11 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "github.com/authorizerdev/authorizer/server/graph/model"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
)
|
||||||
|
|
||||||
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
|
|
||||||
@@ -19,23 +24,20 @@ type VerificationRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
|
||||||
token := v.Token
|
id := v.ID
|
||||||
createdAt := v.CreatedAt
|
if strings.Contains(id, Collections.WebhookLog+"/") {
|
||||||
updatedAt := v.UpdatedAt
|
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
|
||||||
email := v.Email
|
}
|
||||||
nonce := v.Nonce
|
|
||||||
redirectURI := v.RedirectURI
|
|
||||||
expires := v.ExpiresAt
|
|
||||||
identifier := v.Identifier
|
|
||||||
return &model.VerificationRequest{
|
return &model.VerificationRequest{
|
||||||
ID: v.ID,
|
ID: id,
|
||||||
Token: &token,
|
Token: refs.NewStringRef(v.Token),
|
||||||
Identifier: &identifier,
|
Identifier: refs.NewStringRef(v.Identifier),
|
||||||
Expires: &expires,
|
Expires: refs.NewInt64Ref(v.ExpiresAt),
|
||||||
Email: &email,
|
Email: refs.NewStringRef(v.Email),
|
||||||
Nonce: &nonce,
|
Nonce: refs.NewStringRef(v.Nonce),
|
||||||
RedirectURI: &redirectURI,
|
RedirectURI: refs.NewStringRef(v.RedirectURI),
|
||||||
CreatedAt: &createdAt,
|
CreatedAt: refs.NewInt64Ref(v.CreatedAt),
|
||||||
UpdatedAt: &updatedAt,
|
UpdatedAt: refs.NewInt64Ref(v.UpdatedAt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,10 @@ package models
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
@@ -20,16 +22,23 @@ type Webhook struct {
|
|||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AsAPIWebhook to return webhook as graphql response object
|
||||||
func (w *Webhook) AsAPIWebhook() *model.Webhook {
|
func (w *Webhook) AsAPIWebhook() *model.Webhook {
|
||||||
headersMap := make(map[string]interface{})
|
headersMap := make(map[string]interface{})
|
||||||
json.Unmarshal([]byte(w.Headers), &headersMap)
|
json.Unmarshal([]byte(w.Headers), &headersMap)
|
||||||
|
|
||||||
|
id := w.ID
|
||||||
|
if strings.Contains(id, Collections.Webhook+"/") {
|
||||||
|
id = strings.TrimPrefix(id, Collections.Webhook+"/")
|
||||||
|
}
|
||||||
|
|
||||||
return &model.Webhook{
|
return &model.Webhook{
|
||||||
ID: w.ID,
|
ID: id,
|
||||||
EventName: &w.EventName,
|
EventName: refs.NewStringRef(w.EventName),
|
||||||
Endpoint: &w.EndPoint,
|
Endpoint: refs.NewStringRef(w.EndPoint),
|
||||||
Headers: headersMap,
|
Headers: headersMap,
|
||||||
Enabled: &w.Enabled,
|
Enabled: refs.NewBoolRef(w.Enabled),
|
||||||
CreatedAt: &w.CreatedAt,
|
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
|
||||||
UpdatedAt: &w.UpdatedAt,
|
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,30 +1,39 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "github.com/authorizerdev/authorizer/server/graph/model"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
)
|
||||||
|
|
||||||
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
|
||||||
|
|
||||||
// WebhookLog model for db
|
// WebhookLog model for db
|
||||||
type WebhookLog struct {
|
type WebhookLog struct {
|
||||||
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty"` // for arangodb
|
||||||
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id"`
|
||||||
HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status"`
|
HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status"`
|
||||||
Response string `gorm:"type:text" json:"response" bson:"response" cql:"response"`
|
Response string `gorm:"type:text" json:"response" bson:"response" cql:"response"`
|
||||||
Request string `gorm:"type:text" json:"request" bson:"request" cql:"request"`
|
Request string `gorm:"type:text" json:"request" bson:"request" cql:"request"`
|
||||||
WebhookID string `gorm:"type:char(36),index:" json:"webhook_id" bson:"webhook_id" cql:"webhook_id"`
|
WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id"`
|
||||||
Webhook Webhook `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" bson:"-" cql:"-"`
|
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
||||||
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at"`
|
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
||||||
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AsAPIWebhookLog to return webhook log as graphql response object
|
||||||
func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog {
|
func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog {
|
||||||
|
id := w.ID
|
||||||
|
if strings.Contains(id, Collections.WebhookLog+"/") {
|
||||||
|
id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
|
||||||
|
}
|
||||||
return &model.WebhookLog{
|
return &model.WebhookLog{
|
||||||
ID: w.ID,
|
ID: id,
|
||||||
HTTPStatus: &w.HttpStatus,
|
HTTPStatus: refs.NewInt64Ref(w.HttpStatus),
|
||||||
Response: &w.Response,
|
Response: refs.NewStringRef(w.Response),
|
||||||
Request: &w.Request,
|
Request: refs.NewStringRef(w.Request),
|
||||||
WebhookID: &w.WebhookID,
|
WebhookID: refs.NewStringRef(w.WebhookID),
|
||||||
CreatedAt: &w.CreatedAt,
|
CreatedAt: refs.NewInt64Ref(w.CreatedAt),
|
||||||
UpdatedAt: &w.UpdatedAt,
|
UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
151
server/db/providers/arangodb/email_template.go
Normal file
151
server/db/providers/arangodb/email_template.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package arangodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arangodb/go-driver"
|
||||||
|
arangoDriver "github.com/arangodb/go-driver"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
|
||||||
|
_, err := emailTemplateCollection.CreateDocument(ctx, emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
|
||||||
|
meta, err := emailTemplateCollection.UpdateDocument(ctx, emailTemplate.Key, emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = meta.Key
|
||||||
|
emailTemplate.ID = meta.ID.String()
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
emailTemplates := []*model.EmailTemplate{}
|
||||||
|
|
||||||
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.EmailTemplate, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
|
sctx := driver.WithQueryFullCount(ctx)
|
||||||
|
cursor, err := p.db.Query(sctx, query, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = cursor.Statistics().FullCount()
|
||||||
|
|
||||||
|
for {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
meta, err := cursor.ReadDocument(ctx, &emailTemplate)
|
||||||
|
|
||||||
|
if arangoDriver.IsNoMoreDocuments(err) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.Key != "" {
|
||||||
|
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: emailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
query := fmt.Sprintf("FOR d in %s FILTER d._key == @email_template_id RETURN d", models.Collections.EmailTemplate)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"email_template_id": emailTemplateID,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if emailTemplate.Key == "" {
|
||||||
|
return nil, fmt.Errorf("email template not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(ctx, &emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.EmailTemplate)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"event_name": eventName,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if emailTemplate.Key == "" {
|
||||||
|
return nil, fmt.Errorf("email template not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(ctx, &emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
eventTemplateCollection, _ := p.db.Collection(ctx, models.Collections.EmailTemplate)
|
||||||
|
_, err := eventTemplateCollection.RemoveDocument(ctx, emailTemplate.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@@ -134,6 +134,20 @@ func NewProvider() (*provider, error) {
|
|||||||
Sparse: true,
|
Sparse: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
emailTemplateCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.EmailTemplate)
|
||||||
|
if !emailTemplateCollectionExists {
|
||||||
|
_, err = arangodb.CreateCollection(ctx, models.Collections.EmailTemplate, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplateCollection, _ := arangodb.Collection(nil, models.Collections.EmailTemplate)
|
||||||
|
emailTemplateCollection.EnsureHashIndex(ctx, []string{"event_name"}, &arangoDriver.EnsureHashIndexOptions{
|
||||||
|
Unique: true,
|
||||||
|
Sparse: true,
|
||||||
|
})
|
||||||
|
|
||||||
return &provider{
|
return &provider{
|
||||||
db: arangodb,
|
db: arangodb,
|
||||||
}, err
|
}, err
|
||||||
|
@@ -2,7 +2,6 @@ package arangodb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -24,17 +23,3 @@ func (p *provider) AddSession(ctx context.Context, session models.Session) error
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
|
|
||||||
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @userId REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
|
|
||||||
bindVars := map[string]interface{}{
|
|
||||||
"userId": userId,
|
|
||||||
}
|
|
||||||
cursor, err := p.db.Query(ctx, query, bindVars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer cursor.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -63,6 +63,16 @@ func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @user_id REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
|
||||||
|
bindVars := map[string]interface{}{
|
||||||
|
"user_id": user.ID,
|
||||||
|
}
|
||||||
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -83,7 +83,7 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
|
|||||||
// GetWebhookByID to get webhook by id
|
// GetWebhookByID to get webhook by id
|
||||||
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
|
||||||
var webhook models.Webhook
|
var webhook models.Webhook
|
||||||
query := fmt.Sprintf("FOR d in %s FILTER d._id == @webhook_id RETURN d", models.Collections.Webhook)
|
query := fmt.Sprintf("FOR d in %s FILTER d._key == @webhook_id RETURN d", models.Collections.Webhook)
|
||||||
bindVars := map[string]interface{}{
|
bindVars := map[string]interface{}{
|
||||||
"webhook_id": webhookID,
|
"webhook_id": webhookID,
|
||||||
}
|
}
|
||||||
@@ -146,9 +146,9 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
query := fmt.Sprintf("FOR d in %s FILTER d.event_id == @event_id REMOVE { _key: d._key }", models.Collections.WebhookLog)
|
query := fmt.Sprintf("FOR d IN %s FILTER d.webhook_id == @webhook_id REMOVE { _key: d._key } IN %s", models.Collections.WebhookLog, models.Collections.WebhookLog)
|
||||||
bindVars := map[string]interface{}{
|
bindVars := map[string]interface{}{
|
||||||
"event_id": webhook.ID,
|
"webhook_id": webhook.ID,
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor, err := p.db.Query(ctx, query, bindVars)
|
cursor, err := p.db.Query(ctx, query, bindVars)
|
||||||
|
@@ -37,11 +37,12 @@ func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Paginat
|
|||||||
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
|
query := fmt.Sprintf("FOR d in %s SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
|
||||||
|
|
||||||
if webhookID != "" {
|
if webhookID != "" {
|
||||||
query = fmt.Sprintf("FOR d in %s FILTER d.webhook_id == @webhookID SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
|
query = fmt.Sprintf("FOR d in %s FILTER d.webhook_id == @webhook_id SORT d.created_at DESC LIMIT %d, %d RETURN d", models.Collections.WebhookLog, pagination.Offset, pagination.Limit)
|
||||||
bindVariables = map[string]interface{}{
|
bindVariables = map[string]interface{}{
|
||||||
"webhook_id": webhookID,
|
"webhook_id": webhookID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sctx := driver.WithQueryFullCount(ctx)
|
sctx := driver.WithQueryFullCount(ctx)
|
||||||
cursor, err := p.db.Query(sctx, query, bindVariables)
|
cursor, err := p.db.Query(sctx, query, bindVariables)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
159
server/db/providers/cassandradb/email_template.go
Normal file
159
server/db/providers/cassandradb/email_template.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
package cassandradb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/gocql/gocql"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
existingEmailTemplate, _ := p.GetEmailTemplateByEventName(ctx, emailTemplate.EventName)
|
||||||
|
if existingEmailTemplate != nil {
|
||||||
|
return nil, fmt.Errorf("Email template with %s event_name already exists", emailTemplate.EventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, subject, template, created_at, updated_at) VALUES ('%s', '%s', '%s','%s', %d, %d)", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID, emailTemplate.EventName, emailTemplate.Subject, emailTemplate.Template, emailTemplate.CreatedAt, emailTemplate.UpdatedAt)
|
||||||
|
err := p.db.Query(insertQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// use decoder instead of json.Unmarshall, because it converts int64 -> float64 after unmarshalling
|
||||||
|
decoder := json.NewDecoder(strings.NewReader(string(bytes)))
|
||||||
|
decoder.UseNumber()
|
||||||
|
emailTemplateMap := map[string]interface{}{}
|
||||||
|
err = decoder.Decode(&emailTemplateMap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFields := ""
|
||||||
|
for key, value := range emailTemplateMap {
|
||||||
|
if key == "_id" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if key == "_key" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if value == nil {
|
||||||
|
updateFields += fmt.Sprintf("%s = null,", key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
valueType := reflect.TypeOf(value)
|
||||||
|
if valueType.Name() == "string" {
|
||||||
|
updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string))
|
||||||
|
} else {
|
||||||
|
updateFields += fmt.Sprintf("%s = %v, ", key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateFields = strings.Trim(updateFields, " ")
|
||||||
|
updateFields = strings.TrimSuffix(updateFields, ",")
|
||||||
|
|
||||||
|
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.EmailTemplate, updateFields, emailTemplate.ID)
|
||||||
|
err = p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
emailTemplates := []*model.EmailTemplate{}
|
||||||
|
paginationClone := pagination
|
||||||
|
|
||||||
|
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.EmailTemplate)
|
||||||
|
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// there is no offset in cassandra
|
||||||
|
// so we fetch till limit + offset
|
||||||
|
// and return the results from offset to limit
|
||||||
|
query := fmt.Sprintf("SELECT id, event_name, subject, template, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.EmailTemplate, pagination.Limit+pagination.Offset)
|
||||||
|
|
||||||
|
scanner := p.db.Query(query).Iter().Scanner()
|
||||||
|
counter := int64(0)
|
||||||
|
for scanner.Next() {
|
||||||
|
if counter >= pagination.Offset {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
err := scanner.Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: emailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
query := fmt.Sprintf(`SELECT id, event_name, subject, template, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.EmailTemplate, emailTemplateID)
|
||||||
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
query := fmt.Sprintf(`SELECT id, event_name, subject, template, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.EmailTemplate, eventName)
|
||||||
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&emailTemplate.ID, &emailTemplate.EventName, &emailTemplate.Subject, &emailTemplate.Template, &emailTemplate.CreatedAt, &emailTemplate.UpdatedAt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.EmailTemplate, emailTemplate.ID)
|
||||||
|
err := p.db.Query(query).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -143,6 +143,11 @@ func NewProvider() (*provider, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
sessionIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_session_user_id ON %s.%s (user_id)", KeySpace, models.Collections.Session)
|
||||||
|
err = session.Query(sessionIndexQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
userCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, email_verified_at bigint, password text, signup_methods text, given_name text, family_name text, middle_name text, nickname text, gender text, birthdate text, phone_number text, phone_number_verified_at bigint, picture text, roles text, updated_at bigint, created_at bigint, revoked_timestamp bigint, PRIMARY KEY (id))", KeySpace, models.Collections.User)
|
userCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, email text, email_verified_at bigint, password text, signup_methods text, given_name text, family_name text, middle_name text, nickname text, gender text, birthdate text, phone_number text, phone_number_verified_at bigint, picture text, roles text, updated_at bigint, created_at bigint, revoked_timestamp bigint, PRIMARY KEY (id))", KeySpace, models.Collections.User)
|
||||||
err = session.Query(userCollectionQuery).Exec()
|
err = session.Query(userCollectionQuery).Exec()
|
||||||
@@ -177,7 +182,7 @@ func NewProvider() (*provider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
webhookCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, endpoint text, enabled boolean, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.Webhook)
|
webhookCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, endpoint text, enabled boolean, headers text, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.Webhook)
|
||||||
err = session.Query(webhookCollectionQuery).Exec()
|
err = session.Query(webhookCollectionQuery).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -199,6 +204,23 @@ func NewProvider() (*provider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emailTemplateCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, event_name text, template text, updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.EmailTemplate)
|
||||||
|
err = session.Query(emailTemplateCollectionQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emailTemplateIndexQuery := fmt.Sprintf("CREATE INDEX IF NOT EXISTS authorizer_email_template_event_name ON %s.%s (event_name)", KeySpace, models.Collections.EmailTemplate)
|
||||||
|
err = session.Query(emailTemplateIndexQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// add subject on email_templates table
|
||||||
|
emailTemplateAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD subject text;`, KeySpace, models.Collections.EmailTemplate)
|
||||||
|
err = session.Query(emailTemplateAlterQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &provider{
|
return &provider{
|
||||||
db: session,
|
db: session,
|
||||||
}, err
|
}, err
|
||||||
|
@@ -25,13 +25,3 @@ func (p *provider) AddSession(ctx context.Context, session models.Session) error
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
|
|
||||||
deleteSessionQuery := fmt.Sprintf("DELETE FROM %s WHERE user_id = '%s'", KeySpace+"."+models.Collections.Session, userId)
|
|
||||||
err := p.db.Query(deleteSessionQuery).Exec()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -102,6 +102,10 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == "_key" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if value == nil {
|
if value == nil {
|
||||||
updateFields += fmt.Sprintf("%s = null,", key)
|
updateFields += fmt.Sprintf("%s = null,", key)
|
||||||
continue
|
continue
|
||||||
@@ -131,7 +135,29 @@ func (p *provider) UpdateUser(ctx context.Context, user models.User) (models.Use
|
|||||||
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
||||||
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, user.ID)
|
query := fmt.Sprintf("DELETE FROM %s WHERE id = '%s'", KeySpace+"."+models.Collections.User, user.ID)
|
||||||
err := p.db.Query(query).Exec()
|
err := p.db.Query(query).Exec()
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
getSessionsQuery := fmt.Sprintf("SELECT id FROM %s WHERE user_id = '%s' ALLOW FILTERING", KeySpace+"."+models.Collections.Session, user.ID)
|
||||||
|
scanner := p.db.Query(getSessionsQuery).Iter().Scanner()
|
||||||
|
sessionIDs := ""
|
||||||
|
for scanner.Next() {
|
||||||
|
var wlID string
|
||||||
|
err = scanner.Scan(&wlID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sessionIDs += fmt.Sprintf("'%s',", wlID)
|
||||||
|
}
|
||||||
|
sessionIDs = strings.TrimSuffix(sessionIDs, ",")
|
||||||
|
deleteSessionQuery := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", KeySpace+"."+models.Collections.Session, sessionIDs)
|
||||||
|
err = p.db.Query(deleteSessionQuery).Exec()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUsers to get list of users from database
|
// ListUsers to get list of users from database
|
||||||
@@ -171,7 +197,7 @@ func (p *provider) ListUsers(ctx context.Context, pagination model.Pagination) (
|
|||||||
// GetUserByEmail to get user information from database using email address
|
// GetUserByEmail to get user information from database using email address
|
||||||
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
|
func (p *provider) GetUserByEmail(ctx context.Context, email string) (models.User, error) {
|
||||||
var user models.User
|
var user models.User
|
||||||
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1", KeySpace+"."+models.Collections.User, email)
|
query := fmt.Sprintf("SELECT id, email, email_verified_at, password, signup_methods, given_name, family_name, middle_name, nickname, birthdate, phone_number, phone_number_verified_at, picture, roles, revoked_timestamp, created_at, updated_at FROM %s WHERE email = '%s' LIMIT 1 ALLOW FILTERING", KeySpace+"."+models.Collections.User, email)
|
||||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.CreatedAt, &user.UpdatedAt)
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&user.ID, &user.Email, &user.EmailVerifiedAt, &user.Password, &user.SignupMethods, &user.GivenName, &user.FamilyName, &user.MiddleName, &user.Nickname, &user.Birthdate, &user.PhoneNumber, &user.PhoneNumberVerifiedAt, &user.Picture, &user.Roles, &user.RevokedTimestamp, &user.CreatedAt, &user.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return user, err
|
return user, err
|
||||||
|
@@ -24,6 +24,11 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
|
|||||||
webhook.CreatedAt = time.Now().Unix()
|
webhook.CreatedAt = time.Now().Unix()
|
||||||
webhook.UpdatedAt = time.Now().Unix()
|
webhook.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
existingHook, _ := p.GetWebhookByEventName(ctx, webhook.EventName)
|
||||||
|
if existingHook != nil {
|
||||||
|
return nil, fmt.Errorf("Webhook with %s event_name already exists", webhook.EventName)
|
||||||
|
}
|
||||||
|
|
||||||
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
|
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
|
||||||
err := p.db.Query(insertQuery).Exec()
|
err := p.db.Query(insertQuery).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -56,6 +61,10 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key == "_key" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if value == nil {
|
if value == nil {
|
||||||
updateFields += fmt.Sprintf("%s = null,", key)
|
updateFields += fmt.Sprintf("%s = null,", key)
|
||||||
continue
|
continue
|
||||||
@@ -72,7 +81,6 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
|
|||||||
updateFields = strings.TrimSuffix(updateFields, ",")
|
updateFields = strings.TrimSuffix(updateFields, ",")
|
||||||
|
|
||||||
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, updateFields, webhook.ID)
|
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, updateFields, webhook.ID)
|
||||||
|
|
||||||
err = p.db.Query(query).Exec()
|
err = p.db.Query(query).Exec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -130,7 +138,7 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
|
|||||||
// GetWebhookByEventName to get webhook by event_name
|
// GetWebhookByEventName to get webhook by event_name
|
||||||
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) {
|
||||||
var webhook models.Webhook
|
var webhook models.Webhook
|
||||||
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1`, KeySpace+"."+models.Collections.Webhook, eventName)
|
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName)
|
||||||
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
|
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -146,7 +154,19 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
query = fmt.Sprintf("DELETE FROM %s WHERE webhook_id = '%s'", KeySpace+"."+models.Collections.WebhookLog, webhook.ID)
|
getWebhookLogQuery := fmt.Sprintf("SELECT id FROM %s WHERE webhook_id = '%s' ALLOW FILTERING", KeySpace+"."+models.Collections.WebhookLog, webhook.ID)
|
||||||
|
scanner := p.db.Query(getWebhookLogQuery).Iter().Scanner()
|
||||||
|
webhookLogIDs := ""
|
||||||
|
for scanner.Next() {
|
||||||
|
var wlID string
|
||||||
|
err = scanner.Scan(&wlID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
webhookLogIDs += fmt.Sprintf("'%s',", wlID)
|
||||||
|
}
|
||||||
|
webhookLogIDs = strings.TrimSuffix(webhookLogIDs, ",")
|
||||||
|
query = fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", KeySpace+"."+models.Collections.WebhookLog, webhookLogIDs)
|
||||||
err = p.db.Query(query).Exec()
|
err = p.db.Query(query).Exec()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -40,8 +40,8 @@ func (p *provider) ListWebhookLogs(ctx context.Context, pagination model.Paginat
|
|||||||
query := fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.WebhookLog, pagination.Limit+pagination.Offset)
|
query := fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.WebhookLog, pagination.Limit+pagination.Offset)
|
||||||
|
|
||||||
if webhookID != "" {
|
if webhookID != "" {
|
||||||
totalCountQuery = fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE webhook_id='%s'`, KeySpace+"."+models.Collections.WebhookLog, webhookID)
|
totalCountQuery = fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE webhook_id='%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.WebhookLog, webhookID)
|
||||||
query = fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s WHERE webhook_id = '%s' LIMIT %d", KeySpace+"."+models.Collections.WebhookLog, webhookID, pagination.Limit+pagination.Offset)
|
query = fmt.Sprintf("SELECT id, http_status, response, request, webhook_id, created_at, updated_at FROM %s WHERE webhook_id = '%s' LIMIT %d ALLOW FILTERING", KeySpace+"."+models.Collections.WebhookLog, webhookID, pagination.Limit+pagination.Offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
|
||||||
|
115
server/db/providers/mongodb/email_template.go
Normal file
115
server/db/providers/mongodb/email_template.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
_, err := emailTemplateCollection.InsertOne(ctx, emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
_, err := emailTemplateCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": emailTemplate.ID}}, bson.M{"$set": emailTemplate}, options.MergeUpdateOptions())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
var emailTemplates []*model.EmailTemplate
|
||||||
|
opts := options.Find()
|
||||||
|
opts.SetLimit(pagination.Limit)
|
||||||
|
opts.SetSkip(pagination.Offset)
|
||||||
|
opts.SetSort(bson.M{"created_at": -1})
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
count, err := emailTemplateCollection.CountDocuments(ctx, bson.M{}, options.Count())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone.Total = count
|
||||||
|
|
||||||
|
cursor, err := emailTemplateCollection.Find(ctx, bson.M{}, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(ctx)
|
||||||
|
|
||||||
|
for cursor.Next(ctx) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
err := cursor.Decode(&emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
emailTemplates = append(emailTemplates, emailTemplate.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: emailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
err := emailTemplateCollection.FindOne(ctx, bson.M{"_id": emailTemplateID}).Decode(&emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
err := emailTemplateCollection.FindOne(ctx, bson.M{"event_name": eventName}).Decode(&emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
emailTemplateCollection := p.db.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
_, err := emailTemplateCollection.DeleteOne(nil, bson.M{"_id": emailTemplate.ID}, options.Delete())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -101,6 +101,15 @@ func NewProvider() (*provider, error) {
|
|||||||
},
|
},
|
||||||
}, options.CreateIndexes())
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.EmailTemplate, options.CreateCollection())
|
||||||
|
emailTemplateCollection := mongodb.Collection(models.Collections.EmailTemplate, options.Collection())
|
||||||
|
emailTemplateCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
{
|
||||||
|
Keys: bson.M{"event_name": 1},
|
||||||
|
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
return &provider{
|
return &provider{
|
||||||
db: mongodb,
|
db: mongodb,
|
||||||
}, nil
|
}, nil
|
||||||
|
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,13 +25,3 @@ func (p *provider) AddSession(ctx context.Context, session models.Session) error
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
|
|
||||||
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
|
|
||||||
_, err := sessionCollection.DeleteMany(ctx, bson.M{"user_id": userId}, options.Delete())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -57,6 +57,12 @@ func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
|
||||||
|
_, err = sessionCollection.DeleteMany(ctx, bson.M{"user_id": user.ID}, options.Delete())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -111,7 +111,7 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
|
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
|
||||||
_, err = webhookLogCollection.DeleteOne(nil, bson.M{"webhook_id": webhook.ID}, options.Delete())
|
_, err = webhookLogCollection.DeleteMany(nil, bson.M{"webhook_id": webhook.ID}, options.Delete())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
48
server/db/providers/provider_template/email_template.go
Normal file
48
server/db/providers/provider_template/email_template.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package provider_template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
return nil
|
||||||
|
}
|
@@ -34,8 +34,6 @@ type Provider interface {
|
|||||||
|
|
||||||
// AddSession to save session information in database
|
// AddSession to save session information in database
|
||||||
AddSession(ctx context.Context, session models.Session) error
|
AddSession(ctx context.Context, session models.Session) error
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
DeleteSession(ctx context.Context, userId string) error
|
|
||||||
|
|
||||||
// AddEnv to save environment information in database
|
// AddEnv to save environment information in database
|
||||||
AddEnv(ctx context.Context, env models.Env) (models.Env, error)
|
AddEnv(ctx context.Context, env models.Env) (models.Env, error)
|
||||||
@@ -61,4 +59,17 @@ type Provider interface {
|
|||||||
AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error)
|
AddWebhookLog(ctx context.Context, webhookLog models.WebhookLog) (*model.WebhookLog, error)
|
||||||
// ListWebhookLogs to list webhook logs
|
// ListWebhookLogs to list webhook logs
|
||||||
ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error)
|
ListWebhookLogs(ctx context.Context, pagination model.Pagination, webhookID string) (*model.WebhookLogs, error)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error)
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error)
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error)
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error)
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error)
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error
|
||||||
}
|
}
|
||||||
|
100
server/db/providers/sql/email_template.go
Normal file
100
server/db/providers/sql/email_template.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package sql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddEmailTemplate to add EmailTemplate
|
||||||
|
func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
if emailTemplate.ID == "" {
|
||||||
|
emailTemplate.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate.Key = emailTemplate.ID
|
||||||
|
emailTemplate.CreatedAt = time.Now().Unix()
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
res := p.db.Create(&emailTemplate)
|
||||||
|
if res.Error != nil {
|
||||||
|
return nil, res.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEmailTemplate to update EmailTemplate
|
||||||
|
func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate models.EmailTemplate) (*model.EmailTemplate, error) {
|
||||||
|
emailTemplate.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
res := p.db.Save(&emailTemplate)
|
||||||
|
if res.Error != nil {
|
||||||
|
return nil, res.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListEmailTemplates to list EmailTemplate
|
||||||
|
func (p *provider) ListEmailTemplate(ctx context.Context, pagination model.Pagination) (*model.EmailTemplates, error) {
|
||||||
|
var emailTemplates []models.EmailTemplate
|
||||||
|
|
||||||
|
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&emailTemplates)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
var total int64
|
||||||
|
totalRes := p.db.Model(&models.EmailTemplate{}).Count(&total)
|
||||||
|
if totalRes.Error != nil {
|
||||||
|
return nil, totalRes.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
paginationClone := pagination
|
||||||
|
paginationClone.Total = total
|
||||||
|
|
||||||
|
responseEmailTemplates := []*model.EmailTemplate{}
|
||||||
|
for _, w := range emailTemplates {
|
||||||
|
responseEmailTemplates = append(responseEmailTemplates, w.AsAPIEmailTemplate())
|
||||||
|
}
|
||||||
|
return &model.EmailTemplates{
|
||||||
|
Pagination: &paginationClone,
|
||||||
|
EmailTemplates: responseEmailTemplates,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByID to get EmailTemplate by id
|
||||||
|
func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
|
||||||
|
result := p.db.Where("id = ?", emailTemplateID).First(&emailTemplate)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmailTemplateByEventName to get EmailTemplate by event_name
|
||||||
|
func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
|
||||||
|
var emailTemplate models.EmailTemplate
|
||||||
|
|
||||||
|
result := p.db.Where("event_name = ?", eventName).First(&emailTemplate)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
return emailTemplate.AsAPIEmailTemplate(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteEmailTemplate to delete EmailTemplate
|
||||||
|
func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
|
||||||
|
result := p.db.Delete(&models.EmailTemplate{
|
||||||
|
ID: emailTemplate.ID,
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@@ -50,7 +50,7 @@ func NewProvider() (*provider, error) {
|
|||||||
sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig)
|
sqlDB, err = gorm.Open(postgres.Open(dbURL), ormConfig)
|
||||||
case constants.DbTypeSqlite:
|
case constants.DbTypeSqlite:
|
||||||
sqlDB, err = gorm.Open(sqlite.Open(dbURL), ormConfig)
|
sqlDB, err = gorm.Open(sqlite.Open(dbURL), ormConfig)
|
||||||
case constants.DbTypeMysql, constants.DbTypeMariaDB:
|
case constants.DbTypeMysql, constants.DbTypeMariaDB, constants.DbTypePlanetScaleDB:
|
||||||
sqlDB, err = gorm.Open(mysql.Open(dbURL), ormConfig)
|
sqlDB, err = gorm.Open(mysql.Open(dbURL), ormConfig)
|
||||||
case constants.DbTypeSqlserver:
|
case constants.DbTypeSqlserver:
|
||||||
sqlDB, err = gorm.Open(sqlserver.Open(dbURL), ormConfig)
|
sqlDB, err = gorm.Open(sqlserver.Open(dbURL), ormConfig)
|
||||||
@@ -60,7 +60,7 @@ func NewProvider() (*provider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{})
|
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{}, &models.Webhook{}, models.WebhookLog{}, models.EmailTemplate{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -27,13 +27,3 @@ func (p *provider) AddSession(ctx context.Context, session models.Session) error
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteSession to delete session information from database
|
|
||||||
func (p *provider) DeleteSession(ctx context.Context, userId string) error {
|
|
||||||
result := p.db.Where("user_id = ?", userId).Delete(&models.Session{})
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@@ -63,6 +63,11 @@ func (p *provider) DeleteUser(ctx context.Context, user models.User) error {
|
|||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = p.db.Where("user_id = ?", user.ID).Delete(&models.Session{})
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -70,7 +70,7 @@ func InviteEmail(toEmail, token, verificationURL, redirectURI string) error {
|
|||||||
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
|
||||||
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
|
||||||
<p>Hi there 👋</p>
|
<p>Hi there 👋</p>
|
||||||
<p>Join us! You are invited to sign-up for <b>{{.org_name}}</b>. Please accept the invitation by clicking the clicking the button below.</p> <br/>
|
<p>Join us! You are invited to sign-up for <b>{{.org_name}}</b>. Please accept the invitation by clicking the button below.</p> <br/>
|
||||||
<a
|
<a
|
||||||
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a>
|
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Get Started</a>
|
||||||
</td>
|
</td>
|
||||||
|
4
server/env/persist_env.go
vendored
4
server/env/persist_env.go
vendored
@@ -113,7 +113,7 @@ func PersistEnv() error {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
env, err := db.Provider.GetEnv(ctx)
|
env, err := db.Provider.GetEnv(ctx)
|
||||||
// config not found in db
|
// config not found in db
|
||||||
if err != nil {
|
if err != nil || env.EnvData == "" {
|
||||||
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
|
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
|
||||||
hash := uuid.New().String()[:36-4]
|
hash := uuid.New().String()[:36-4]
|
||||||
err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, hash)
|
err := memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEncryptionKey, hash)
|
||||||
@@ -174,7 +174,7 @@ func PersistEnv() error {
|
|||||||
|
|
||||||
err = json.Unmarshal(decryptedConfigs, &storeData)
|
err = json.Unmarshal(decryptedConfigs, &storeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Error while unmarshalling env data: ", err)
|
log.Debug("Error while un-marshalling env data: ", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
type AddEmailTemplateRequest struct {
|
||||||
|
EventName string `json:"event_name"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
Template string `json:"template"`
|
||||||
|
}
|
||||||
|
|
||||||
type AddWebhookRequest struct {
|
type AddWebhookRequest struct {
|
||||||
EventName string `json:"event_name"`
|
EventName string `json:"event_name"`
|
||||||
Endpoint string `json:"endpoint"`
|
Endpoint string `json:"endpoint"`
|
||||||
@@ -26,10 +32,28 @@ type AuthResponse struct {
|
|||||||
User *User `json:"user"`
|
User *User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteEmailTemplateRequest struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteUserInput struct {
|
type DeleteUserInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EmailTemplate struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
EventName string `json:"event_name"`
|
||||||
|
Template string `json:"template"`
|
||||||
|
Subject string `json:"subject"`
|
||||||
|
CreatedAt *int64 `json:"created_at"`
|
||||||
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailTemplates struct {
|
||||||
|
Pagination *Pagination `json:"pagination"`
|
||||||
|
EmailTemplates []*EmailTemplate `json:"EmailTemplates"`
|
||||||
|
}
|
||||||
|
|
||||||
type Env struct {
|
type Env struct {
|
||||||
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
@@ -206,14 +230,21 @@ type TestEndpointRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TestEndpointResponse struct {
|
type TestEndpointResponse struct {
|
||||||
HTTPStatus *int64 `json:"http_status"`
|
HTTPStatus *int64 `json:"http_status"`
|
||||||
Response map[string]interface{} `json:"response"`
|
Response *string `json:"response"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateAccessInput struct {
|
type UpdateAccessInput struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateEmailTemplateRequest struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
EventName *string `json:"event_name"`
|
||||||
|
Template *string `json:"template"`
|
||||||
|
Subject *string `json:"subject"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateEnvInput struct {
|
type UpdateEnvInput struct {
|
||||||
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
AccessTokenExpiryTime *string `json:"ACCESS_TOKEN_EXPIRY_TIME"`
|
||||||
AdminSecret *string `json:"ADMIN_SECRET"`
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
|
@@ -150,6 +150,55 @@ type GenerateJWTKeysResponse {
|
|||||||
private_key: String
|
private_key: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Webhook {
|
||||||
|
id: ID!
|
||||||
|
event_name: String
|
||||||
|
endpoint: String
|
||||||
|
enabled: Boolean
|
||||||
|
headers: Map
|
||||||
|
created_at: Int64
|
||||||
|
updated_at: Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Webhooks {
|
||||||
|
pagination: Pagination!
|
||||||
|
webhooks: [Webhook!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookLog {
|
||||||
|
id: ID!
|
||||||
|
http_status: Int64
|
||||||
|
response: String
|
||||||
|
request: String
|
||||||
|
webhook_id: ID
|
||||||
|
created_at: Int64
|
||||||
|
updated_at: Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestEndpointResponse {
|
||||||
|
http_status: Int64
|
||||||
|
response: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebhookLogs {
|
||||||
|
pagination: Pagination!
|
||||||
|
webhook_logs: [WebhookLog!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailTemplate {
|
||||||
|
id: ID!
|
||||||
|
event_name: String!
|
||||||
|
template: String!
|
||||||
|
subject: String!
|
||||||
|
created_at: Int64
|
||||||
|
updated_at: Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailTemplates {
|
||||||
|
pagination: Pagination!
|
||||||
|
EmailTemplates: [EmailTemplate!]!
|
||||||
|
}
|
||||||
|
|
||||||
input UpdateEnvInput {
|
input UpdateEnvInput {
|
||||||
ACCESS_TOKEN_EXPIRY_TIME: String
|
ACCESS_TOKEN_EXPIRY_TIME: String
|
||||||
ADMIN_SECRET: String
|
ADMIN_SECRET: String
|
||||||
@@ -324,38 +373,8 @@ input GenerateJWTKeysInput {
|
|||||||
type: String!
|
type: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Webhook {
|
|
||||||
id: ID!
|
|
||||||
event_name: String
|
|
||||||
endpoint: String
|
|
||||||
enabled: Boolean
|
|
||||||
headers: Map
|
|
||||||
created_at: Int64
|
|
||||||
updated_at: Int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type Webhooks {
|
|
||||||
pagination: Pagination!
|
|
||||||
webhooks: [Webhook!]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type WebhookLog {
|
|
||||||
id: ID!
|
|
||||||
http_status: Int64
|
|
||||||
response: String
|
|
||||||
request: String
|
|
||||||
webhook_id: ID
|
|
||||||
created_at: Int64
|
|
||||||
updated_at: Int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestEndpointResponse {
|
|
||||||
http_status: Int64
|
|
||||||
response: Map
|
|
||||||
}
|
|
||||||
|
|
||||||
input ListWebhookLogRequest {
|
input ListWebhookLogRequest {
|
||||||
pagination: PaginationInput!
|
pagination: PaginationInput
|
||||||
webhook_id: String
|
webhook_id: String
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,9 +403,21 @@ input TestEndpointRequest {
|
|||||||
headers: Map
|
headers: Map
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebhookLogs {
|
input AddEmailTemplateRequest {
|
||||||
pagination: Pagination!
|
event_name: String!
|
||||||
webhook_logs: [WebhookLog!]!
|
subject: String!
|
||||||
|
template: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateEmailTemplateRequest {
|
||||||
|
id: ID!
|
||||||
|
event_name: String
|
||||||
|
template: String
|
||||||
|
subject: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteEmailTemplateRequest {
|
||||||
|
id: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
@@ -415,6 +446,9 @@ type Mutation {
|
|||||||
_update_webhook(params: UpdateWebhookRequest!): Response!
|
_update_webhook(params: UpdateWebhookRequest!): Response!
|
||||||
_delete_webhook(params: WebhookRequest!): Response!
|
_delete_webhook(params: WebhookRequest!): Response!
|
||||||
_test_endpoint(params: TestEndpointRequest!): TestEndpointResponse!
|
_test_endpoint(params: TestEndpointRequest!): TestEndpointResponse!
|
||||||
|
_add_email_template(params: AddEmailTemplateRequest!): Response!
|
||||||
|
_update_email_template(params: UpdateEmailTemplateRequest!): Response!
|
||||||
|
_delete_email_template(params: DeleteEmailTemplateRequest!): Response!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
@@ -429,5 +463,6 @@ type Query {
|
|||||||
_env: Env!
|
_env: Env!
|
||||||
_webhook(params: WebhookRequest!): Webhook!
|
_webhook(params: WebhookRequest!): Webhook!
|
||||||
_webhooks(params: PaginatedInput): Webhooks!
|
_webhooks(params: PaginatedInput): Webhooks!
|
||||||
_webhook_logs(params: ListWebhookLogRequest!): WebhookLogs!
|
_webhook_logs(params: ListWebhookLogRequest): WebhookLogs!
|
||||||
|
_email_templates(params: PaginatedInput): EmailTemplates!
|
||||||
}
|
}
|
||||||
|
@@ -107,6 +107,18 @@ func (r *mutationResolver) TestEndpoint(ctx context.Context, params model.TestEn
|
|||||||
return resolvers.TestEndpointResolver(ctx, params)
|
return resolvers.TestEndpointResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) AddEmailTemplate(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) {
|
||||||
|
return resolvers.AddEmailTemplateResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) UpdateEmailTemplate(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) {
|
||||||
|
return resolvers.UpdateEmailTemplateResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) DeleteEmailTemplate(ctx context.Context, params model.DeleteEmailTemplateRequest) (*model.Response, error) {
|
||||||
|
return resolvers.DeleteEmailTemplateResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
||||||
return resolvers.MetaResolver(ctx)
|
return resolvers.MetaResolver(ctx)
|
||||||
}
|
}
|
||||||
@@ -147,10 +159,14 @@ func (r *queryResolver) Webhooks(ctx context.Context, params *model.PaginatedInp
|
|||||||
return resolvers.WebhooksResolver(ctx, params)
|
return resolvers.WebhooksResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) WebhookLogs(ctx context.Context, params model.ListWebhookLogRequest) (*model.WebhookLogs, error) {
|
func (r *queryResolver) WebhookLogs(ctx context.Context, params *model.ListWebhookLogRequest) (*model.WebhookLogs, error) {
|
||||||
return resolvers.WebhookLogsResolver(ctx, params)
|
return resolvers.WebhookLogsResolver(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) EmailTemplates(ctx context.Context, params *model.PaginatedInput) (*model.EmailTemplates, error) {
|
||||||
|
return resolvers.EmailTemplatesResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
// Mutation returns generated.MutationResolver implementation.
|
// Mutation returns generated.MutationResolver implementation.
|
||||||
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
|
@@ -320,12 +320,60 @@ func processGithubUserInfo(code string) (models.User, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
picture := userRawData["avatar_url"]
|
picture := userRawData["avatar_url"]
|
||||||
|
email := userRawData["email"]
|
||||||
|
|
||||||
|
if email == "" {
|
||||||
|
type GithubUserEmails struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
Primary bool `json:"primary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch using /users/email endpoint
|
||||||
|
req, err := http.NewRequest("GET", constants.GithubUserEmails, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to create github emails request: ", err)
|
||||||
|
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
|
||||||
|
}
|
||||||
|
req.Header = http.Header{
|
||||||
|
"Authorization": []string{fmt.Sprintf("token %s", oauth2Token.AccessToken)},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to request github user email: ", err)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to read github user email response body: ", err)
|
||||||
|
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
|
||||||
|
}
|
||||||
|
if response.StatusCode >= 400 {
|
||||||
|
log.Debug("Failed to request github user email: ", string(body))
|
||||||
|
return user, fmt.Errorf("failed to request github user info: %s", string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
emailData := []GithubUserEmails{}
|
||||||
|
err = json.Unmarshal(body, &emailData)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to parse github user email: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userEmail := range emailData {
|
||||||
|
email = userEmail.Email
|
||||||
|
if userEmail.Primary {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user = models.User{
|
user = models.User{
|
||||||
GivenName: &firstName,
|
GivenName: &firstName,
|
||||||
FamilyName: &lastName,
|
FamilyName: &lastName,
|
||||||
Picture: &picture,
|
Picture: &picture,
|
||||||
Email: userRawData["email"],
|
Email: email,
|
||||||
}
|
}
|
||||||
|
|
||||||
return user, nil
|
return user, nil
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
package stores
|
package stores
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnvStore struct to store the env variables
|
// EnvStore struct to store the env variables
|
||||||
@@ -23,12 +20,10 @@ func NewEnvStore() *EnvStore {
|
|||||||
|
|
||||||
// UpdateEnvStore to update the whole env store object
|
// UpdateEnvStore to update the whole env store object
|
||||||
func (e *EnvStore) UpdateStore(store map[string]interface{}) {
|
func (e *EnvStore) UpdateStore(store map[string]interface{}) {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
e.mutex.Lock()
|
||||||
e.mutex.Lock()
|
defer e.mutex.Unlock()
|
||||||
defer e.mutex.Unlock()
|
|
||||||
}
|
|
||||||
// just override the keys + new keys
|
|
||||||
|
|
||||||
|
// just override the keys + new keys
|
||||||
for key, value := range store {
|
for key, value := range store {
|
||||||
e.store[key] = value
|
e.store[key] = value
|
||||||
}
|
}
|
||||||
@@ -46,9 +41,8 @@ func (e *EnvStore) Get(key string) interface{} {
|
|||||||
|
|
||||||
// Set sets the value of the key in env store
|
// Set sets the value of the key in env store
|
||||||
func (e *EnvStore) Set(key string, value interface{}) {
|
func (e *EnvStore) Set(key string, value interface{}) {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
e.mutex.Lock()
|
||||||
e.mutex.Lock()
|
defer e.mutex.Unlock()
|
||||||
defer e.mutex.Unlock()
|
|
||||||
}
|
|
||||||
e.store[key] = value
|
e.store[key] = value
|
||||||
}
|
}
|
||||||
|
@@ -1,11 +1,8 @@
|
|||||||
package stores
|
package stores
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SessionStore struct to store the env variables
|
// SessionStore struct to store the env variables
|
||||||
@@ -29,10 +26,9 @@ func (s *SessionStore) Get(key, subKey string) string {
|
|||||||
|
|
||||||
// Set sets the value of the key in state store
|
// Set sets the value of the key in state store
|
||||||
func (s *SessionStore) Set(key string, subKey, value string) {
|
func (s *SessionStore) Set(key string, subKey, value string) {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
s.mutex.Lock()
|
||||||
s.mutex.Lock()
|
defer s.mutex.Unlock()
|
||||||
defer s.mutex.Unlock()
|
|
||||||
}
|
|
||||||
if _, ok := s.store[key]; !ok {
|
if _, ok := s.store[key]; !ok {
|
||||||
s.store[key] = make(map[string]string)
|
s.store[key] = make(map[string]string)
|
||||||
}
|
}
|
||||||
@@ -41,19 +37,15 @@ func (s *SessionStore) Set(key string, subKey, value string) {
|
|||||||
|
|
||||||
// RemoveAll all values for given key
|
// RemoveAll all values for given key
|
||||||
func (s *SessionStore) RemoveAll(key string) {
|
func (s *SessionStore) RemoveAll(key string) {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
s.mutex.Lock()
|
||||||
s.mutex.Lock()
|
defer s.mutex.Unlock()
|
||||||
defer s.mutex.Unlock()
|
|
||||||
}
|
|
||||||
delete(s.store, key)
|
delete(s.store, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove value for given key and subkey
|
// Remove value for given key and subkey
|
||||||
func (s *SessionStore) Remove(key, subKey string) {
|
func (s *SessionStore) Remove(key, subKey string) {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
s.mutex.Lock()
|
||||||
s.mutex.Lock()
|
defer s.mutex.Unlock()
|
||||||
defer s.mutex.Unlock()
|
|
||||||
}
|
|
||||||
if _, ok := s.store[key]; ok {
|
if _, ok := s.store[key]; ok {
|
||||||
delete(s.store[key], subKey)
|
delete(s.store[key], subKey)
|
||||||
}
|
}
|
||||||
@@ -69,11 +61,8 @@ func (s *SessionStore) GetAll(key string) map[string]string {
|
|||||||
|
|
||||||
// RemoveByNamespace to delete session for a given namespace example google,github
|
// RemoveByNamespace to delete session for a given namespace example google,github
|
||||||
func (s *SessionStore) RemoveByNamespace(namespace string) error {
|
func (s *SessionStore) RemoveByNamespace(namespace string) error {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
s.mutex.Lock()
|
||||||
s.mutex.Lock()
|
defer s.mutex.Unlock()
|
||||||
defer s.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
for key := range s.store {
|
for key := range s.store {
|
||||||
if strings.Contains(key, namespace+":") {
|
if strings.Contains(key, namespace+":") {
|
||||||
delete(s.store, key)
|
delete(s.store, key)
|
||||||
|
@@ -1,10 +1,7 @@
|
|||||||
package stores
|
package stores
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// StateStore struct to store the env variables
|
// StateStore struct to store the env variables
|
||||||
@@ -28,19 +25,16 @@ func (s *StateStore) Get(key string) string {
|
|||||||
|
|
||||||
// Set sets the value of the key in state store
|
// Set sets the value of the key in state store
|
||||||
func (s *StateStore) Set(key string, value string) {
|
func (s *StateStore) Set(key string, value string) {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
s.mutex.Lock()
|
||||||
s.mutex.Lock()
|
defer s.mutex.Unlock()
|
||||||
defer s.mutex.Unlock()
|
|
||||||
}
|
|
||||||
s.store[key] = value
|
s.store[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the key from state store
|
// Remove removes the key from state store
|
||||||
func (s *StateStore) Remove(key string) {
|
func (s *StateStore) Remove(key string) {
|
||||||
if os.Getenv("ENV") != constants.TestEnv {
|
s.mutex.Lock()
|
||||||
s.mutex.Lock()
|
defer s.mutex.Unlock()
|
||||||
defer s.mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(s.store, key)
|
delete(s.store, key)
|
||||||
}
|
}
|
||||||
|
14
server/refs/bool.go
Normal file
14
server/refs/bool.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package refs
|
||||||
|
|
||||||
|
// NewBoolRef returns a reference to a bool with given value
|
||||||
|
func NewBoolRef(v bool) *bool {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolValue returns the value of the given bool ref
|
||||||
|
func BoolValue(r *bool) bool {
|
||||||
|
if r == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return *r
|
||||||
|
}
|
14
server/refs/int.go
Normal file
14
server/refs/int.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package refs
|
||||||
|
|
||||||
|
// NewInt64Ref returns a reference to a int64 with given value
|
||||||
|
func NewInt64Ref(v int64) *int64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64Value returns the value of the given bool ref
|
||||||
|
func Int64Value(r *int64) int64 {
|
||||||
|
if r == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return *r
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package utils
|
package refs
|
||||||
|
|
||||||
// NewStringRef returns a reference to a string with given value
|
// NewStringRef returns a reference to a string with given value
|
||||||
func NewStringRef(v string) *string {
|
func NewStringRef(v string) *string {
|
||||||
@@ -16,15 +16,4 @@ func StringValue(r *string, defaultValue ...string) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBoolRef returns a reference to a bool with given value
|
|
||||||
func NewBoolRef(v bool) *bool {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolValue returns the value of the given bool ref
|
|
||||||
func BoolValue(r *bool) bool {
|
|
||||||
if r == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return *r
|
|
||||||
}
|
|
58
server/resolvers/add_email_template.go
Normal file
58
server/resolvers/add_email_template.go
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO add template validator
|
||||||
|
|
||||||
|
// AddEmailTemplateResolver resolver for add email template mutation
|
||||||
|
func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get GinContext: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
log.Debug("Not logged in as super admin")
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validators.IsValidEmailTemplateEventName(params.EventName) {
|
||||||
|
log.Debug("Invalid Event Name: ", params.EventName)
|
||||||
|
return nil, fmt.Errorf("invalid event name %s", params.EventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(params.Subject) == "" {
|
||||||
|
return nil, fmt.Errorf("empty subject not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(params.Template) == "" {
|
||||||
|
return nil, fmt.Errorf("empty template not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Provider.AddEmailTemplate(ctx, models.EmailTemplate{
|
||||||
|
EventName: params.EventName,
|
||||||
|
Template: params.Template,
|
||||||
|
Subject: params.Subject,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to add email template: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Response{
|
||||||
|
Message: `Email template added successfully`,
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
@@ -32,6 +33,11 @@ func AddWebhookResolver(ctx context.Context, params model.AddWebhookRequest) (*m
|
|||||||
return nil, fmt.Errorf("invalid event name %s", params.EventName)
|
return nil, fmt.Errorf("invalid event name %s", params.EventName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(params.Endpoint) == "" {
|
||||||
|
log.Debug("empty endpoint not allowed")
|
||||||
|
return nil, fmt.Errorf("empty endpoint not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
headerBytes, err := json.Marshal(params.Headers)
|
headerBytes, err := json.Marshal(params.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
49
server/resolvers/delete_email_template.go
Normal file
49
server/resolvers/delete_email_template.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeleteEmailTemplateResolver resolver to delete email template and its relevant logs
|
||||||
|
func DeleteEmailTemplateResolver(ctx context.Context, params model.DeleteEmailTemplateRequest) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get GinContext: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
log.Debug("Not logged in as super admin")
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.ID == "" {
|
||||||
|
log.Debug("email template is required")
|
||||||
|
return nil, fmt.Errorf("email template ID required")
|
||||||
|
}
|
||||||
|
|
||||||
|
log := log.WithField("email_template_id", params.ID)
|
||||||
|
|
||||||
|
emailTemplate, err := db.Provider.GetEmailTemplateByID(ctx, params.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("failed to get email template: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Provider.DeleteEmailTemplate(ctx, emailTemplate)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("failed to delete email template: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Response{
|
||||||
|
Message: "Email templated deleted successfully",
|
||||||
|
}, nil
|
||||||
|
}
|
35
server/resolvers/email_templates.go
Normal file
35
server/resolvers/email_templates.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmailTemplatesResolver resolver for getting the list of email templates based on pagination
|
||||||
|
func EmailTemplatesResolver(ctx context.Context, params *model.PaginatedInput) (*model.EmailTemplates, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get GinContext: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
log.Debug("Not logged in as super admin")
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
pagination := utils.GetPagination(params)
|
||||||
|
|
||||||
|
emailTemplates, err := db.Provider.ListEmailTemplate(ctx, pagination)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("failed to get email templates: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return emailTemplates, nil
|
||||||
|
}
|
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
@@ -38,10 +39,10 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := store[constants.EnvKeyAccessTokenExpiryTime]; ok {
|
if val, ok := store[constants.EnvKeyAccessTokenExpiryTime]; ok {
|
||||||
res.AccessTokenExpiryTime = utils.NewStringRef(val.(string))
|
res.AccessTokenExpiryTime = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyAdminSecret]; ok {
|
if val, ok := store[constants.EnvKeyAdminSecret]; ok {
|
||||||
res.AdminSecret = utils.NewStringRef(val.(string))
|
res.AdminSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyClientID]; ok {
|
if val, ok := store[constants.EnvKeyClientID]; ok {
|
||||||
res.ClientID = val.(string)
|
res.ClientID = val.(string)
|
||||||
@@ -50,103 +51,103 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
|
|||||||
res.ClientSecret = val.(string)
|
res.ClientSecret = val.(string)
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyDatabaseURL]; ok {
|
if val, ok := store[constants.EnvKeyDatabaseURL]; ok {
|
||||||
res.DatabaseURL = utils.NewStringRef(val.(string))
|
res.DatabaseURL = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyDatabaseName]; ok {
|
if val, ok := store[constants.EnvKeyDatabaseName]; ok {
|
||||||
res.DatabaseName = utils.NewStringRef(val.(string))
|
res.DatabaseName = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyDatabaseType]; ok {
|
if val, ok := store[constants.EnvKeyDatabaseType]; ok {
|
||||||
res.DatabaseType = utils.NewStringRef(val.(string))
|
res.DatabaseType = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyDatabaseUsername]; ok {
|
if val, ok := store[constants.EnvKeyDatabaseUsername]; ok {
|
||||||
res.DatabaseUsername = utils.NewStringRef(val.(string))
|
res.DatabaseUsername = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyDatabasePassword]; ok {
|
if val, ok := store[constants.EnvKeyDatabasePassword]; ok {
|
||||||
res.DatabasePassword = utils.NewStringRef(val.(string))
|
res.DatabasePassword = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyDatabaseHost]; ok {
|
if val, ok := store[constants.EnvKeyDatabaseHost]; ok {
|
||||||
res.DatabaseHost = utils.NewStringRef(val.(string))
|
res.DatabaseHost = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyDatabasePort]; ok {
|
if val, ok := store[constants.EnvKeyDatabasePort]; ok {
|
||||||
res.DatabasePort = utils.NewStringRef(val.(string))
|
res.DatabasePort = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyCustomAccessTokenScript]; ok {
|
if val, ok := store[constants.EnvKeyCustomAccessTokenScript]; ok {
|
||||||
res.CustomAccessTokenScript = utils.NewStringRef(val.(string))
|
res.CustomAccessTokenScript = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeySmtpHost]; ok {
|
if val, ok := store[constants.EnvKeySmtpHost]; ok {
|
||||||
res.SMTPHost = utils.NewStringRef(val.(string))
|
res.SMTPHost = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeySmtpPort]; ok {
|
if val, ok := store[constants.EnvKeySmtpPort]; ok {
|
||||||
res.SMTPPort = utils.NewStringRef(val.(string))
|
res.SMTPPort = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeySmtpUsername]; ok {
|
if val, ok := store[constants.EnvKeySmtpUsername]; ok {
|
||||||
res.SMTPUsername = utils.NewStringRef(val.(string))
|
res.SMTPUsername = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeySmtpPassword]; ok {
|
if val, ok := store[constants.EnvKeySmtpPassword]; ok {
|
||||||
res.SMTPPassword = utils.NewStringRef(val.(string))
|
res.SMTPPassword = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeySenderEmail]; ok {
|
if val, ok := store[constants.EnvKeySenderEmail]; ok {
|
||||||
res.SenderEmail = utils.NewStringRef(val.(string))
|
res.SenderEmail = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyJwtType]; ok {
|
if val, ok := store[constants.EnvKeyJwtType]; ok {
|
||||||
res.JwtType = utils.NewStringRef(val.(string))
|
res.JwtType = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyJwtSecret]; ok {
|
if val, ok := store[constants.EnvKeyJwtSecret]; ok {
|
||||||
res.JwtSecret = utils.NewStringRef(val.(string))
|
res.JwtSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyJwtRoleClaim]; ok {
|
if val, ok := store[constants.EnvKeyJwtRoleClaim]; ok {
|
||||||
res.JwtRoleClaim = utils.NewStringRef(val.(string))
|
res.JwtRoleClaim = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyJwtPublicKey]; ok {
|
if val, ok := store[constants.EnvKeyJwtPublicKey]; ok {
|
||||||
res.JwtPublicKey = utils.NewStringRef(val.(string))
|
res.JwtPublicKey = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyJwtPrivateKey]; ok {
|
if val, ok := store[constants.EnvKeyJwtPrivateKey]; ok {
|
||||||
res.JwtPrivateKey = utils.NewStringRef(val.(string))
|
res.JwtPrivateKey = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyAppURL]; ok {
|
if val, ok := store[constants.EnvKeyAppURL]; ok {
|
||||||
res.AppURL = utils.NewStringRef(val.(string))
|
res.AppURL = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyRedisURL]; ok {
|
if val, ok := store[constants.EnvKeyRedisURL]; ok {
|
||||||
res.RedisURL = utils.NewStringRef(val.(string))
|
res.RedisURL = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyResetPasswordURL]; ok {
|
if val, ok := store[constants.EnvKeyResetPasswordURL]; ok {
|
||||||
res.ResetPasswordURL = utils.NewStringRef(val.(string))
|
res.ResetPasswordURL = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyGoogleClientID]; ok {
|
if val, ok := store[constants.EnvKeyGoogleClientID]; ok {
|
||||||
res.GoogleClientID = utils.NewStringRef(val.(string))
|
res.GoogleClientID = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyGoogleClientSecret]; ok {
|
if val, ok := store[constants.EnvKeyGoogleClientSecret]; ok {
|
||||||
res.GoogleClientSecret = utils.NewStringRef(val.(string))
|
res.GoogleClientSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyFacebookClientID]; ok {
|
if val, ok := store[constants.EnvKeyFacebookClientID]; ok {
|
||||||
res.FacebookClientID = utils.NewStringRef(val.(string))
|
res.FacebookClientID = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyFacebookClientSecret]; ok {
|
if val, ok := store[constants.EnvKeyFacebookClientSecret]; ok {
|
||||||
res.FacebookClientSecret = utils.NewStringRef(val.(string))
|
res.FacebookClientSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyGithubClientID]; ok {
|
if val, ok := store[constants.EnvKeyGithubClientID]; ok {
|
||||||
res.GithubClientID = utils.NewStringRef(val.(string))
|
res.GithubClientID = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyGithubClientSecret]; ok {
|
if val, ok := store[constants.EnvKeyGithubClientSecret]; ok {
|
||||||
res.GithubClientSecret = utils.NewStringRef(val.(string))
|
res.GithubClientSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyLinkedInClientID]; ok {
|
if val, ok := store[constants.EnvKeyLinkedInClientID]; ok {
|
||||||
res.LinkedinClientID = utils.NewStringRef(val.(string))
|
res.LinkedinClientID = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyLinkedInClientSecret]; ok {
|
if val, ok := store[constants.EnvKeyLinkedInClientSecret]; ok {
|
||||||
res.LinkedinClientSecret = utils.NewStringRef(val.(string))
|
res.LinkedinClientSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyAppleClientID]; ok {
|
if val, ok := store[constants.EnvKeyAppleClientID]; ok {
|
||||||
res.AppleClientID = utils.NewStringRef(val.(string))
|
res.AppleClientID = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyAppleClientSecret]; ok {
|
if val, ok := store[constants.EnvKeyAppleClientSecret]; ok {
|
||||||
res.AppleClientSecret = utils.NewStringRef(val.(string))
|
res.AppleClientSecret = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
|
if val, ok := store[constants.EnvKeyOrganizationName]; ok {
|
||||||
res.OrganizationName = utils.NewStringRef(val.(string))
|
res.OrganizationName = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
if val, ok := store[constants.EnvKeyOrganizationLogo]; ok {
|
if val, ok := store[constants.EnvKeyOrganizationLogo]; ok {
|
||||||
res.OrganizationLogo = utils.NewStringRef(val.(string))
|
res.OrganizationLogo = refs.NewStringRef(val.(string))
|
||||||
}
|
}
|
||||||
|
|
||||||
// string slice vars
|
// string slice vars
|
||||||
|
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -41,8 +42,8 @@ func TestEndpointResolver(ctx context.Context, params model.TestEndpointRequest)
|
|||||||
Email: "test_endpoint@foo.com",
|
Email: "test_endpoint@foo.com",
|
||||||
EmailVerified: true,
|
EmailVerified: true,
|
||||||
SignupMethods: constants.AuthRecipeMethodMagicLinkLogin,
|
SignupMethods: constants.AuthRecipeMethodMagicLinkLogin,
|
||||||
GivenName: utils.NewStringRef("Foo"),
|
GivenName: refs.NewStringRef("Foo"),
|
||||||
FamilyName: utils.NewStringRef("Bar"),
|
FamilyName: refs.NewStringRef("Bar"),
|
||||||
}
|
}
|
||||||
|
|
||||||
userBytes, err := json.Marshal(user)
|
userBytes, err := json.Marshal(user)
|
||||||
@@ -95,15 +96,9 @@ func TestEndpointResolver(ctx context.Context, params model.TestEndpointRequest)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response := map[string]interface{}{}
|
|
||||||
if err := json.Unmarshal(body, &response); err != nil {
|
|
||||||
log.Debug("error un-marshalling response: ", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
statusCode := int64(resp.StatusCode)
|
statusCode := int64(resp.StatusCode)
|
||||||
return &model.TestEndpointResponse{
|
return &model.TestEndpointResponse{
|
||||||
HTTPStatus: &statusCode,
|
HTTPStatus: &statusCode,
|
||||||
Response: response,
|
Response: refs.NewStringRef(string(body)),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
78
server/resolvers/update_email_template.go
Normal file
78
server/resolvers/update_email_template.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO add template validator
|
||||||
|
|
||||||
|
// UpdateEmailTemplateResolver resolver for update email template mutation
|
||||||
|
func UpdateEmailTemplateResolver(ctx context.Context, params model.UpdateEmailTemplateRequest) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("Failed to get GinContext: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !token.IsSuperAdmin(gc) {
|
||||||
|
log.Debug("Not logged in as super admin")
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplate, err := db.Provider.GetEmailTemplateByID(ctx, params.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("failed to get email template: ", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplateDetails := models.EmailTemplate{
|
||||||
|
ID: emailTemplate.ID,
|
||||||
|
Key: emailTemplate.ID,
|
||||||
|
EventName: emailTemplate.EventName,
|
||||||
|
CreatedAt: refs.Int64Value(emailTemplate.CreatedAt),
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.EventName != nil && emailTemplateDetails.EventName != refs.StringValue(params.EventName) {
|
||||||
|
if isValid := validators.IsValidEmailTemplateEventName(refs.StringValue(params.EventName)); !isValid {
|
||||||
|
log.Debug("invalid event name: ", refs.StringValue(params.EventName))
|
||||||
|
return nil, fmt.Errorf("invalid event name %s", refs.StringValue(params.EventName))
|
||||||
|
}
|
||||||
|
emailTemplateDetails.EventName = refs.StringValue(params.EventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Subject != nil && emailTemplateDetails.Subject != refs.StringValue(params.Subject) {
|
||||||
|
if strings.TrimSpace(refs.StringValue(params.Subject)) == "" {
|
||||||
|
log.Debug("empty subject not allowed")
|
||||||
|
return nil, fmt.Errorf("empty subject not allowed")
|
||||||
|
}
|
||||||
|
emailTemplateDetails.Subject = refs.StringValue(params.Subject)
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Template != nil && emailTemplateDetails.Template != refs.StringValue(params.Template) {
|
||||||
|
if strings.TrimSpace(refs.StringValue(params.Template)) == "" {
|
||||||
|
log.Debug("empty template not allowed")
|
||||||
|
return nil, fmt.Errorf("empty template not allowed")
|
||||||
|
}
|
||||||
|
emailTemplateDetails.Template = refs.StringValue(params.Template)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.Provider.UpdateEmailTemplate(ctx, emailTemplateDetails)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Response{
|
||||||
|
Message: `Email template updated successfully.`,
|
||||||
|
}, nil
|
||||||
|
}
|
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -45,7 +46,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validate if all params are not empty
|
// validate if all params are not empty
|
||||||
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil {
|
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil && params.NewPassword == nil && params.ConfirmNewPassword == nil {
|
||||||
log.Debug("All params are empty")
|
log.Debug("All params are empty")
|
||||||
return res, fmt.Errorf("please enter at least one param to update")
|
return res, fmt.Errorf("please enter at least one param to update")
|
||||||
}
|
}
|
||||||
@@ -61,70 +62,108 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.GivenName != nil && user.GivenName != params.GivenName {
|
if params.GivenName != nil && refs.StringValue(user.GivenName) != refs.StringValue(params.GivenName) {
|
||||||
user.GivenName = params.GivenName
|
user.GivenName = params.GivenName
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.FamilyName != nil && user.FamilyName != params.FamilyName {
|
if params.FamilyName != nil && refs.StringValue(user.FamilyName) != refs.StringValue(params.FamilyName) {
|
||||||
user.FamilyName = params.FamilyName
|
user.FamilyName = params.FamilyName
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.MiddleName != nil && user.MiddleName != params.MiddleName {
|
if params.MiddleName != nil && refs.StringValue(user.MiddleName) != refs.StringValue(params.MiddleName) {
|
||||||
user.MiddleName = params.MiddleName
|
user.MiddleName = params.MiddleName
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Nickname != nil && user.Nickname != params.Nickname {
|
if params.Nickname != nil && refs.StringValue(user.Nickname) != refs.StringValue(params.Nickname) {
|
||||||
user.Nickname = params.Nickname
|
user.Nickname = params.Nickname
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Birthdate != nil && user.Birthdate != params.Birthdate {
|
if params.Birthdate != nil && refs.StringValue(user.Birthdate) != refs.StringValue(params.Birthdate) {
|
||||||
user.Birthdate = params.Birthdate
|
user.Birthdate = params.Birthdate
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Gender != nil && user.Gender != params.Gender {
|
if params.Gender != nil && refs.StringValue(user.Gender) != refs.StringValue(params.Gender) {
|
||||||
user.Gender = params.Gender
|
user.Gender = params.Gender
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.PhoneNumber != nil && user.PhoneNumber != params.PhoneNumber {
|
if params.PhoneNumber != nil && refs.StringValue(user.PhoneNumber) != refs.StringValue(params.PhoneNumber) {
|
||||||
user.PhoneNumber = params.PhoneNumber
|
user.PhoneNumber = params.PhoneNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Picture != nil && user.Picture != params.Picture {
|
if params.Picture != nil && refs.StringValue(user.Picture) != refs.StringValue(params.Picture) {
|
||||||
user.Picture = params.Picture
|
user.Picture = params.Picture
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.OldPassword != nil {
|
isPasswordChanging := false
|
||||||
if err = bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(*params.OldPassword)); err != nil {
|
if params.NewPassword != nil && params.ConfirmNewPassword == nil {
|
||||||
|
isPasswordChanging = true
|
||||||
|
log.Debug("confirm password is empty")
|
||||||
|
return res, fmt.Errorf("confirm password is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.ConfirmNewPassword != nil && params.NewPassword == nil {
|
||||||
|
isPasswordChanging = true
|
||||||
|
log.Debug("new password is empty")
|
||||||
|
return res, fmt.Errorf("new password is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.NewPassword != nil && params.ConfirmNewPassword != nil {
|
||||||
|
isPasswordChanging = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPasswordChanging && user.Password != nil && params.OldPassword == nil {
|
||||||
|
log.Debug("old password is empty")
|
||||||
|
return res, fmt.Errorf("old password is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPasswordChanging && user.Password != nil && params.OldPassword != nil {
|
||||||
|
if err = bcrypt.CompareHashAndPassword([]byte(refs.StringValue(user.Password)), []byte(refs.StringValue(params.OldPassword))); err != nil {
|
||||||
log.Debug("Failed to compare hash and old password: ", err)
|
log.Debug("Failed to compare hash and old password: ", err)
|
||||||
return res, fmt.Errorf("incorrect old password")
|
return res, fmt.Errorf("incorrect old password")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if params.NewPassword == nil {
|
shouldAddBasicSignUpMethod := false
|
||||||
log.Debug("Failed to get new password: ")
|
isBasicAuthDisabled, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication)
|
||||||
return res, fmt.Errorf("new password is required")
|
if err != nil {
|
||||||
|
log.Debug("Error getting basic auth disabled: ", err)
|
||||||
|
isBasicAuthDisabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.NewPassword != nil && params.ConfirmNewPassword != nil {
|
||||||
|
if isBasicAuthDisabled {
|
||||||
|
log.Debug("Cannot update password as basic authentication is disabled")
|
||||||
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.ConfirmNewPassword == nil {
|
if refs.StringValue(params.ConfirmNewPassword) != refs.StringValue(params.NewPassword) {
|
||||||
log.Debug("Failed to get confirm new password: ")
|
|
||||||
return res, fmt.Errorf("confirm password is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if *params.ConfirmNewPassword != *params.NewPassword {
|
|
||||||
log.Debug("Failed to compare new password and confirm new password")
|
log.Debug("Failed to compare new password and confirm new password")
|
||||||
return res, fmt.Errorf(`password and confirm password does not match`)
|
return res, fmt.Errorf(`password and confirm password does not match`)
|
||||||
}
|
}
|
||||||
|
|
||||||
password, _ := crypto.EncryptPassword(*params.NewPassword)
|
if user.Password == nil || refs.StringValue(user.Password) == "" {
|
||||||
|
shouldAddBasicSignUpMethod = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validators.IsValidPassword(refs.StringValue(params.NewPassword)); err != nil {
|
||||||
|
log.Debug("Invalid password")
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
password, _ := crypto.EncryptPassword(refs.StringValue(params.NewPassword))
|
||||||
user.Password = &password
|
user.Password = &password
|
||||||
|
|
||||||
|
if shouldAddBasicSignUpMethod {
|
||||||
|
user.SignupMethods = user.SignupMethods + "," + constants.AuthRecipeMethodBasicAuth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hasEmailChanged := false
|
hasEmailChanged := false
|
||||||
|
|
||||||
if params.Email != nil && user.Email != *params.Email {
|
if params.Email != nil && user.Email != refs.StringValue(params.Email) {
|
||||||
// check if valid email
|
// check if valid email
|
||||||
if !validators.IsValidEmail(*params.Email) {
|
if !validators.IsValidEmail(*params.Email) {
|
||||||
log.Debug("Failed to validate email: ", *params.Email)
|
log.Debug("Failed to validate email: ", refs.StringValue(params.Email))
|
||||||
return res, fmt.Errorf("invalid email address")
|
return res, fmt.Errorf("invalid email address")
|
||||||
}
|
}
|
||||||
newEmail := strings.ToLower(*params.Email)
|
newEmail := strings.ToLower(*params.Email)
|
||||||
|
@@ -4,10 +4,12 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
@@ -45,35 +47,35 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
|
|||||||
webhookDetails := models.Webhook{
|
webhookDetails := models.Webhook{
|
||||||
ID: webhook.ID,
|
ID: webhook.ID,
|
||||||
Key: webhook.ID,
|
Key: webhook.ID,
|
||||||
EventName: utils.StringValue(webhook.EventName),
|
EventName: refs.StringValue(webhook.EventName),
|
||||||
EndPoint: utils.StringValue(webhook.Endpoint),
|
EndPoint: refs.StringValue(webhook.Endpoint),
|
||||||
Enabled: utils.BoolValue(webhook.Enabled),
|
Enabled: refs.BoolValue(webhook.Enabled),
|
||||||
Headers: headersString,
|
Headers: headersString,
|
||||||
CreatedAt: *webhook.CreatedAt,
|
CreatedAt: refs.Int64Value(webhook.CreatedAt),
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.EventName != nil && webhookDetails.EventName != utils.StringValue(params.EventName) {
|
if params.EventName != nil && webhookDetails.EventName != refs.StringValue(params.EventName) {
|
||||||
if isValid := validators.IsValidWebhookEventName(utils.StringValue(params.EventName)); !isValid {
|
if isValid := validators.IsValidWebhookEventName(refs.StringValue(params.EventName)); !isValid {
|
||||||
log.Debug("invalid event name: ", utils.StringValue(params.EventName))
|
log.Debug("invalid event name: ", refs.StringValue(params.EventName))
|
||||||
return nil, fmt.Errorf("invalid event name %s", utils.StringValue(params.EventName))
|
return nil, fmt.Errorf("invalid event name %s", refs.StringValue(params.EventName))
|
||||||
}
|
}
|
||||||
webhookDetails.EventName = utils.StringValue(params.EventName)
|
webhookDetails.EventName = refs.StringValue(params.EventName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Endpoint != nil && webhookDetails.EndPoint != utils.StringValue(params.Endpoint) {
|
if params.Endpoint != nil && webhookDetails.EndPoint != refs.StringValue(params.Endpoint) {
|
||||||
webhookDetails.EventName = utils.StringValue(params.EventName)
|
if strings.TrimSpace(refs.StringValue(params.Endpoint)) == "" {
|
||||||
|
log.Debug("empty endpoint not allowed")
|
||||||
|
return nil, fmt.Errorf("empty endpoint not allowed")
|
||||||
|
}
|
||||||
|
webhookDetails.EndPoint = refs.StringValue(params.Endpoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Enabled != nil && webhookDetails.Enabled != utils.BoolValue(params.Enabled) {
|
if params.Enabled != nil && webhookDetails.Enabled != refs.BoolValue(params.Enabled) {
|
||||||
webhookDetails.Enabled = utils.BoolValue(params.Enabled)
|
webhookDetails.Enabled = refs.BoolValue(params.Enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Headers != nil {
|
if params.Headers != nil {
|
||||||
for key, val := range params.Headers {
|
headerBytes, err := json.Marshal(params.Headers)
|
||||||
webhook.Headers[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
headerBytes, err := json.Marshal(webhook.Headers)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("failed to marshall headers: ", err)
|
log.Debug("failed to marshall headers: ", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -6,13 +6,14 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebhookLogsResolver resolver for getting the list of webhook_logs based on pagination & webhook identifier
|
// WebhookLogsResolver resolver for getting the list of webhook_logs based on pagination & webhook identifier
|
||||||
func WebhookLogsResolver(ctx context.Context, params model.ListWebhookLogRequest) (*model.WebhookLogs, error) {
|
func WebhookLogsResolver(ctx context.Context, params *model.ListWebhookLogRequest) (*model.WebhookLogs, error) {
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to get GinContext: ", err)
|
log.Debug("Failed to get GinContext: ", err)
|
||||||
@@ -24,11 +25,20 @@ func WebhookLogsResolver(ctx context.Context, params model.ListWebhookLogRequest
|
|||||||
return nil, fmt.Errorf("unauthorized")
|
return nil, fmt.Errorf("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
pagination := utils.GetPagination(&model.PaginatedInput{
|
var pagination model.Pagination
|
||||||
Pagination: params.Pagination,
|
var webhookID string
|
||||||
})
|
|
||||||
|
|
||||||
webhookLogs, err := db.Provider.ListWebhookLogs(ctx, pagination, utils.StringValue(params.WebhookID))
|
if params != nil {
|
||||||
|
pagination = utils.GetPagination(&model.PaginatedInput{
|
||||||
|
Pagination: params.Pagination,
|
||||||
|
})
|
||||||
|
webhookID = refs.StringValue(params.WebhookID)
|
||||||
|
} else {
|
||||||
|
pagination = utils.GetPagination(nil)
|
||||||
|
webhookID = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookLogs, err := db.Provider.ListWebhookLogs(ctx, pagination, webhookID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("failed to get webhook logs: ", err)
|
log.Debug("failed to get webhook logs: ", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -28,7 +28,7 @@ func WebhooksResolver(ctx context.Context, params *model.PaginatedInput) (*model
|
|||||||
|
|
||||||
webhooks, err := db.Provider.ListWebhook(ctx, pagination)
|
webhooks, err := db.Provider.ListWebhook(ctx, pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("failed to get webhook logs: ", err)
|
log.Debug("failed to get webhooks: ", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return webhooks, nil
|
return webhooks, nil
|
||||||
|
71
server/test/add_email_template_test.go
Normal file
71
server/test/add_email_template_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func addEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
t.Run("should add email templates", func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
h, err := crypto.EncryptPassword(adminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
||||||
|
|
||||||
|
t.Run("should not add email template for invalid event type", func(t *testing.T) {
|
||||||
|
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
|
||||||
|
EventName: "test",
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, emailTemplate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should not add email template for empty template", func(t *testing.T) {
|
||||||
|
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
|
||||||
|
EventName: s.TestInfo.TestEmailTemplateEventTypes[0],
|
||||||
|
Template: " test ",
|
||||||
|
Subject: " ",
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, emailTemplate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should not add email template for empty template", func(t *testing.T) {
|
||||||
|
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
|
||||||
|
EventName: s.TestInfo.TestEmailTemplateEventTypes[0],
|
||||||
|
Template: " ",
|
||||||
|
Subject: "test",
|
||||||
|
})
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, emailTemplate)
|
||||||
|
})
|
||||||
|
for _, eventType := range s.TestInfo.TestEmailTemplateEventTypes {
|
||||||
|
t.Run("should add email template for "+eventType, func(t *testing.T) {
|
||||||
|
emailTemplate, err := resolvers.AddEmailTemplateResolver(ctx, model.AddEmailTemplateRequest{
|
||||||
|
EventName: eventType,
|
||||||
|
Template: "Test email",
|
||||||
|
Subject: "Test email",
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, emailTemplate)
|
||||||
|
assert.NotEmpty(t, emailTemplate.Message)
|
||||||
|
|
||||||
|
et, err := db.Provider.GetEmailTemplateByEventName(ctx, eventType)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, et.EventName, eventType)
|
||||||
|
assert.Equal(t, "Test email", et.Subject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@@ -22,7 +22,7 @@ func addWebhookTest(t *testing.T, s TestSetup) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
||||||
|
|
||||||
for _, eventType := range s.TestInfo.TestEventTypes {
|
for _, eventType := range s.TestInfo.TestWebhookEventTypes {
|
||||||
webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{
|
webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{
|
||||||
EventName: eventType,
|
EventName: eventType,
|
||||||
Endpoint: s.TestInfo.WebhookEndpoint,
|
Endpoint: s.TestInfo.WebhookEndpoint,
|
||||||
|
52
server/test/delete_email_template_test.go
Normal file
52
server/test/delete_email_template_test.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deleteEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
t.Run("should delete email templates", func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
h, err := crypto.EncryptPassword(adminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
||||||
|
|
||||||
|
// get all email templates
|
||||||
|
emailTemplates, err := db.Provider.ListEmailTemplate(ctx, model.Pagination{
|
||||||
|
Limit: 10,
|
||||||
|
Page: 1,
|
||||||
|
Offset: 0,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
for _, e := range emailTemplates.EmailTemplates {
|
||||||
|
res, err := resolvers.DeleteEmailTemplateResolver(ctx, model.DeleteEmailTemplateRequest{
|
||||||
|
ID: e.ID,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, res)
|
||||||
|
assert.NotEmpty(t, res.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
emailTemplates, err = db.Provider.ListEmailTemplate(ctx, model.Pagination{
|
||||||
|
Limit: 10,
|
||||||
|
Page: 1,
|
||||||
|
Offset: 0,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, emailTemplates.EmailTemplates, 0)
|
||||||
|
})
|
||||||
|
}
|
@@ -24,7 +24,11 @@ func deleteWebhookTest(t *testing.T, s TestSetup) {
|
|||||||
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
||||||
|
|
||||||
// get all webhooks
|
// get all webhooks
|
||||||
webhooks, err := db.Provider.ListWebhook(ctx, model.Pagination{})
|
webhooks, err := db.Provider.ListWebhook(ctx, model.Pagination{
|
||||||
|
Limit: 10,
|
||||||
|
Page: 1,
|
||||||
|
Offset: 0,
|
||||||
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
for _, w := range webhooks.Webhooks {
|
for _, w := range webhooks.Webhooks {
|
||||||
@@ -37,12 +41,17 @@ func deleteWebhookTest(t *testing.T, s TestSetup) {
|
|||||||
assert.NotEmpty(t, res.Message)
|
assert.NotEmpty(t, res.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
webhooks, err = db.Provider.ListWebhook(ctx, model.Pagination{})
|
webhooks, err = db.Provider.ListWebhook(ctx, model.Pagination{
|
||||||
|
Limit: 10,
|
||||||
|
Page: 1,
|
||||||
|
Offset: 0,
|
||||||
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, webhooks.Webhooks, 0)
|
assert.Len(t, webhooks.Webhooks, 0)
|
||||||
|
|
||||||
webhookLogs, err := db.Provider.ListWebhookLogs(ctx, model.Pagination{
|
webhookLogs, err := db.Provider.ListWebhookLogs(ctx, model.Pagination{
|
||||||
Limit: 10,
|
Limit: 100,
|
||||||
|
Page: 1,
|
||||||
|
Offset: 0,
|
||||||
}, "")
|
}, "")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, webhookLogs.WebhookLogs, 0)
|
assert.Len(t, webhookLogs.WebhookLogs, 0)
|
||||||
|
29
server/test/email_templates_test.go
Normal file
29
server/test/email_templates_test.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func emailTemplatesTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
t.Run("should get email templates", func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
h, err := crypto.EncryptPassword(adminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
||||||
|
|
||||||
|
emailTemplates, err := resolvers.EmailTemplatesResolver(ctx, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, emailTemplates)
|
||||||
|
assert.Len(t, emailTemplates.EmailTemplates, len(s.TestInfo.TestEmailTemplateEventTypes))
|
||||||
|
})
|
||||||
|
}
|
@@ -2,6 +2,8 @@ package test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,35 +11,66 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/env"
|
"github.com/authorizerdev/authorizer/server/env"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolvers(t *testing.T) {
|
func TestResolvers(t *testing.T) {
|
||||||
databases := map[string]string{
|
databases := map[string]string{
|
||||||
constants.DbTypeSqlite: "../../data.db",
|
constants.DbTypeSqlite: "../../test.db",
|
||||||
// constants.DbTypeArangodb: "http://localhost:8529",
|
constants.DbTypeArangodb: "http://localhost:8529",
|
||||||
// constants.DbTypeMongodb: "mongodb://localhost:27017",
|
constants.DbTypeMongodb: "mongodb://localhost:27017",
|
||||||
// constants.DbTypeCassandraDB: "127.0.0.1:9042",
|
constants.DbTypeScyllaDB: "127.0.0.1:9042",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testDBs := strings.Split(os.Getenv("TEST_DBS"), ",")
|
||||||
|
t.Log("Running tests for following dbs: ", testDBs)
|
||||||
|
for dbType := range databases {
|
||||||
|
if !utils.StringSliceContains(testDBs, dbType) {
|
||||||
|
delete(databases, dbType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.StringSliceContains(testDBs, constants.DbTypeSqlite) && len(testDBs) == 1 {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
t.Log("waiting for docker containers to spun up")
|
||||||
|
// wait for docker containers to spun up
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
testDb := "authorizer_test"
|
||||||
|
s := testSetup()
|
||||||
|
defer s.Server.Close()
|
||||||
|
|
||||||
for dbType, dbURL := range databases {
|
for dbType, dbURL := range databases {
|
||||||
s := testSetup()
|
|
||||||
defer s.Server.Close()
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseURL, dbURL)
|
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseURL, dbURL)
|
||||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseType, dbType)
|
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseType, dbType)
|
||||||
|
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDatabaseName, testDb)
|
||||||
|
os.Setenv(constants.EnvKeyDatabaseURL, dbURL)
|
||||||
|
os.Setenv(constants.EnvKeyDatabaseType, dbType)
|
||||||
|
os.Setenv(constants.EnvKeyDatabaseName, testDb)
|
||||||
|
memorystore.InitRequiredEnv()
|
||||||
|
|
||||||
err := db.InitDB()
|
err := db.InitDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error initializing database: %s", err)
|
t.Errorf("Error initializing database: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean the persisted config for test to use fresh config
|
// clean the persisted config for test to use fresh config
|
||||||
envData, err := db.Provider.GetEnv(ctx)
|
envData, err := db.Provider.GetEnv(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
envData.EnvData = ""
|
envData.EnvData = ""
|
||||||
db.Provider.UpdateEnv(ctx, envData)
|
_, err = db.Provider.UpdateEnv(ctx, envData)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error updating env: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = env.PersistEnv()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error persisting env: %s", err.Error())
|
||||||
}
|
}
|
||||||
env.PersistEnv()
|
|
||||||
|
|
||||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEnv, "test")
|
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyEnv, "test")
|
||||||
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyIsProd, false)
|
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyIsProd, false)
|
||||||
@@ -61,6 +94,10 @@ func TestResolvers(t *testing.T) {
|
|||||||
revokeAccessTest(t, s)
|
revokeAccessTest(t, s)
|
||||||
enableAccessTest(t, s)
|
enableAccessTest(t, s)
|
||||||
generateJWTkeyTest(t, s)
|
generateJWTkeyTest(t, s)
|
||||||
|
addEmailTemplateTest(t, s)
|
||||||
|
updateEmailTemplateTest(t, s)
|
||||||
|
emailTemplatesTest(t, s)
|
||||||
|
deleteEmailTemplateTest(t, s)
|
||||||
|
|
||||||
// user resolvers tests
|
// user resolvers tests
|
||||||
loginTests(t, s)
|
loginTests(t, s)
|
||||||
@@ -78,9 +115,8 @@ func TestResolvers(t *testing.T) {
|
|||||||
inviteUserTest(t, s)
|
inviteUserTest(t, s)
|
||||||
validateJwtTokenTest(t, s)
|
validateJwtTokenTest(t, s)
|
||||||
|
|
||||||
time.Sleep(5 * time.Second) // add sleep for webhooklogs to get generated as they are async
|
webhookLogsTest(t, s) // get logs after above resolver tests are done
|
||||||
webhookLogsTest(t, s) // get logs after above resolver tests are done
|
deleteWebhookTest(t, s) // delete webhooks (admin resolver)
|
||||||
deleteWebhookTest(t, s) // delete webhooks (admin resolver)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,10 +21,11 @@ import (
|
|||||||
|
|
||||||
// common user data to share across tests
|
// common user data to share across tests
|
||||||
type TestData struct {
|
type TestData struct {
|
||||||
Email string
|
Email string
|
||||||
Password string
|
Password string
|
||||||
WebhookEndpoint string
|
WebhookEndpoint string
|
||||||
TestEventTypes []string
|
TestWebhookEventTypes []string
|
||||||
|
TestEmailTemplateEventTypes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestSetup struct {
|
type TestSetup struct {
|
||||||
@@ -59,7 +60,6 @@ func cleanData(email string) {
|
|||||||
dbUser, err := db.Provider.GetUserByEmail(ctx, email)
|
dbUser, err := db.Provider.GetUserByEmail(ctx, email)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
db.Provider.DeleteUser(ctx, dbUser)
|
db.Provider.DeleteUser(ctx, dbUser)
|
||||||
db.Provider.DeleteSession(ctx, dbUser.ID)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,10 +77,11 @@ func createContext(s TestSetup) (*http.Request, context.Context) {
|
|||||||
|
|
||||||
func testSetup() TestSetup {
|
func testSetup() TestSetup {
|
||||||
testData := TestData{
|
testData := TestData{
|
||||||
Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()),
|
Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()),
|
||||||
Password: "Test@123",
|
Password: "Test@123",
|
||||||
WebhookEndpoint: "https://62cbc6738042b16aa7c22df2.mockapi.io/api/v1/webhook",
|
WebhookEndpoint: "https://62cbc6738042b16aa7c22df2.mockapi.io/api/v1/webhook",
|
||||||
TestEventTypes: []string{constants.UserAccessEnabledWebhookEvent, constants.UserAccessRevokedWebhookEvent, constants.UserCreatedWebhookEvent, constants.UserDeletedWebhookEvent, constants.UserLoginWebhookEvent, constants.UserSignUpWebhookEvent},
|
TestWebhookEventTypes: []string{constants.UserAccessEnabledWebhookEvent, constants.UserAccessRevokedWebhookEvent, constants.UserCreatedWebhookEvent, constants.UserDeletedWebhookEvent, constants.UserLoginWebhookEvent, constants.UserSignUpWebhookEvent},
|
||||||
|
TestEmailTemplateEventTypes: []string{constants.VerificationTypeBasicAuthSignup, constants.VerificationTypeForgotPassword, constants.VerificationTypeMagicLinkLogin, constants.VerificationTypeUpdateEmail},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := os.Setenv(constants.EnvKeyEnvPath, "../../.env.test")
|
err := os.Setenv(constants.EnvKeyEnvPath, "../../.env.test")
|
||||||
|
48
server/test/update_email_template_test.go
Normal file
48
server/test/update_email_template_test.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateEmailTemplateTest(t *testing.T, s TestSetup) {
|
||||||
|
t.Helper()
|
||||||
|
t.Run("should update email template", func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
h, err := crypto.EncryptPassword(adminSecret)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
||||||
|
// get email template
|
||||||
|
emailTemplate, err := db.Provider.GetEmailTemplateByEventName(ctx, constants.VerificationTypeBasicAuthSignup)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, emailTemplate)
|
||||||
|
|
||||||
|
res, err := resolvers.UpdateEmailTemplateResolver(ctx, model.UpdateEmailTemplateRequest{
|
||||||
|
ID: emailTemplate.ID,
|
||||||
|
Template: refs.NewStringRef("Updated test template"),
|
||||||
|
Subject: refs.NewStringRef("Updated subject"),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, res)
|
||||||
|
assert.NotEmpty(t, res.Message)
|
||||||
|
|
||||||
|
updatedEmailTemplate, err := db.Provider.GetEmailTemplateByEventName(ctx, constants.VerificationTypeBasicAuthSignup)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, updatedEmailTemplate)
|
||||||
|
assert.Equal(t, emailTemplate.ID, updatedEmailTemplate.ID)
|
||||||
|
assert.Equal(t, updatedEmailTemplate.Template, "Updated test template")
|
||||||
|
assert.Equal(t, updatedEmailTemplate.Subject, "Updated subject")
|
||||||
|
})
|
||||||
|
}
|
@@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,12 +27,16 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
|
|||||||
webhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
|
webhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, webhook)
|
assert.NotNil(t, webhook)
|
||||||
webhook.Headers["x-new-test"] = "new-test"
|
// it should completely replace headers
|
||||||
|
webhook.Headers = map[string]interface{}{
|
||||||
|
"x-new-test": "test",
|
||||||
|
}
|
||||||
|
|
||||||
res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
|
res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
|
||||||
ID: webhook.ID,
|
ID: webhook.ID,
|
||||||
Headers: webhook.Headers,
|
Headers: webhook.Headers,
|
||||||
Enabled: utils.NewBoolRef(false),
|
Enabled: refs.NewBoolRef(false),
|
||||||
|
Endpoint: refs.NewStringRef("https://sometest.com"),
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@@ -43,15 +47,19 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, updatedWebhook)
|
assert.NotNil(t, updatedWebhook)
|
||||||
assert.Equal(t, webhook.ID, updatedWebhook.ID)
|
assert.Equal(t, webhook.ID, updatedWebhook.ID)
|
||||||
assert.Equal(t, utils.StringValue(webhook.EventName), utils.StringValue(updatedWebhook.EventName))
|
assert.Equal(t, refs.StringValue(webhook.EventName), refs.StringValue(updatedWebhook.EventName))
|
||||||
assert.Equal(t, utils.StringValue(webhook.Endpoint), utils.StringValue(updatedWebhook.Endpoint))
|
assert.Len(t, updatedWebhook.Headers, 1)
|
||||||
assert.Len(t, updatedWebhook.Headers, 2)
|
assert.False(t, refs.BoolValue(updatedWebhook.Enabled))
|
||||||
assert.False(t, utils.BoolValue(updatedWebhook.Enabled))
|
for key, val := range updatedWebhook.Headers {
|
||||||
|
assert.Equal(t, val, webhook.Headers[key])
|
||||||
|
}
|
||||||
|
assert.Equal(t, refs.StringValue(updatedWebhook.Endpoint), "https://sometest.com")
|
||||||
|
|
||||||
res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
|
res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
|
||||||
ID: webhook.ID,
|
ID: webhook.ID,
|
||||||
Headers: webhook.Headers,
|
Headers: webhook.Headers,
|
||||||
Enabled: utils.NewBoolRef(true),
|
Enabled: refs.NewBoolRef(true),
|
||||||
|
Endpoint: refs.NewStringRef(s.TestInfo.WebhookEndpoint),
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, res)
|
assert.NotEmpty(t, res)
|
||||||
|
@@ -3,17 +3,19 @@ package test
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/crypto"
|
"github.com/authorizerdev/authorizer/server/crypto"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func webhookLogsTest(t *testing.T, s TestSetup) {
|
func webhookLogsTest(t *testing.T, s TestSetup) {
|
||||||
|
time.Sleep(30 * time.Second) // add sleep for webhooklogs to get generated as they are async
|
||||||
t.Helper()
|
t.Helper()
|
||||||
t.Run("should get webhook logs", func(t *testing.T) {
|
t.Run("should get webhook logs", func(t *testing.T) {
|
||||||
req, ctx := createContext(s)
|
req, ctx := createContext(s)
|
||||||
@@ -23,23 +25,25 @@ func webhookLogsTest(t *testing.T, s TestSetup) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
|
||||||
|
|
||||||
|
webhookLogs, err := resolvers.WebhookLogsResolver(ctx, nil)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Greater(t, len(webhookLogs.WebhookLogs), 1)
|
||||||
|
|
||||||
webhooks, err := resolvers.WebhooksResolver(ctx, nil)
|
webhooks, err := resolvers.WebhooksResolver(ctx, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, webhooks)
|
assert.NotEmpty(t, webhooks)
|
||||||
|
|
||||||
webhookLogs, err := resolvers.WebhookLogsResolver(ctx, model.ListWebhookLogRequest{})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Greater(t, len(webhookLogs.WebhookLogs), 1)
|
|
||||||
|
|
||||||
for _, w := range webhooks.Webhooks {
|
for _, w := range webhooks.Webhooks {
|
||||||
webhookLogs, err := resolvers.WebhookLogsResolver(ctx, model.ListWebhookLogRequest{
|
t.Run(fmt.Sprintf("should get webhook for webhook_id:%s", w.ID), func(t *testing.T) {
|
||||||
WebhookID: &w.ID,
|
webhookLogs, err := resolvers.WebhookLogsResolver(ctx, &model.ListWebhookLogRequest{
|
||||||
|
WebhookID: &w.ID,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.GreaterOrEqual(t, len(webhookLogs.WebhookLogs), 1)
|
||||||
|
for _, wl := range webhookLogs.WebhookLogs {
|
||||||
|
assert.Equal(t, refs.StringValue(wl.WebhookID), w.ID)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.GreaterOrEqual(t, len(webhookLogs.WebhookLogs), 1)
|
|
||||||
for _, wl := range webhookLogs.WebhookLogs {
|
|
||||||
assert.Equal(t, utils.StringValue(wl.WebhookID), w.ID)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,9 +34,9 @@ func webhookTest(t *testing.T, s TestSetup) {
|
|||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, res.ID, webhook.ID)
|
assert.Equal(t, res.ID, webhook.ID)
|
||||||
assert.Equal(t, utils.StringValue(res.Endpoint), utils.StringValue(webhook.Endpoint))
|
assert.Equal(t, refs.StringValue(res.Endpoint), refs.StringValue(webhook.Endpoint))
|
||||||
assert.Equal(t, utils.StringValue(res.EventName), utils.StringValue(webhook.EventName))
|
assert.Equal(t, refs.StringValue(res.EventName), refs.StringValue(webhook.EventName))
|
||||||
assert.Equal(t, utils.BoolValue(res.Enabled), utils.BoolValue(webhook.Enabled))
|
assert.Equal(t, refs.BoolValue(res.Enabled), refs.BoolValue(webhook.Enabled))
|
||||||
assert.Len(t, res.Headers, len(webhook.Headers))
|
assert.Len(t, res.Headers, len(webhook.Headers))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -24,6 +24,6 @@ func webhooksTest(t *testing.T, s TestSetup) {
|
|||||||
webhooks, err := resolvers.WebhooksResolver(ctx, nil)
|
webhooks, err := resolvers.WebhooksResolver(ctx, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, webhooks)
|
assert.NotEmpty(t, webhooks)
|
||||||
assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestEventTypes))
|
assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestWebhookEventTypes))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !BoolValue(webhook.Enabled) {
|
if !refs.BoolValue(webhook.Enabled) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
|
|||||||
}
|
}
|
||||||
|
|
||||||
requestBytesBuffer := bytes.NewBuffer(requestBody)
|
requestBytesBuffer := bytes.NewBuffer(requestBody)
|
||||||
req, err := http.NewRequest("POST", StringValue(webhook.Endpoint), requestBytesBuffer)
|
req, err := http.NewRequest("POST", refs.StringValue(webhook.Endpoint), requestBytesBuffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("error creating webhook post request: ", err)
|
log.Debug("error creating webhook post request: ", err)
|
||||||
return err
|
return err
|
||||||
|
12
server/validators/email_template.go
Normal file
12
server/validators/email_template.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package validators
|
||||||
|
|
||||||
|
import "github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
|
||||||
|
// IsValidEmailTemplateEventName function to validate email template events
|
||||||
|
func IsValidEmailTemplateEventName(eventName string) bool {
|
||||||
|
if eventName != constants.VerificationTypeBasicAuthSignup && eventName != constants.VerificationTypeForgotPassword && eventName != constants.VerificationTypeMagicLinkLogin && eventName != constants.VerificationTypeUpdateEmail {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
@@ -12,9 +12,901 @@
|
|||||||
<script>
|
<script>
|
||||||
window.__authorizer__ = {{.data}}
|
window.__authorizer__ = {{.data}}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.rdw-option-wrapper {
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
padding: 5px;
|
||||||
|
min-width: 25px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0 4px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
background: white;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.rdw-option-wrapper:hover {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-option-wrapper:active {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD inset;
|
||||||
|
}
|
||||||
|
.rdw-option-active {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD inset;
|
||||||
|
}
|
||||||
|
.rdw-option-disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-dropdown-wrapper {
|
||||||
|
height: 30px;
|
||||||
|
background: white;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0 3px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-wrapper:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-wrapper:hover {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-wrapper:active {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD inset;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-carettoopen {
|
||||||
|
height: 0px;
|
||||||
|
width: 0px;
|
||||||
|
position: absolute;
|
||||||
|
top: 35%;
|
||||||
|
right: 10%;
|
||||||
|
border-top: 6px solid black;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-carettoclose {
|
||||||
|
height: 0px;
|
||||||
|
width: 0px;
|
||||||
|
position: absolute;
|
||||||
|
top: 35%;
|
||||||
|
right: 10%;
|
||||||
|
border-bottom: 6px solid black;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-selectedtext {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-optionwrapper {
|
||||||
|
z-index: 100;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
width: 98%;
|
||||||
|
background: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
max-height: 250px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.rdw-dropdown-optionwrapper:hover {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD;
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-dropdownoption-default {
|
||||||
|
min-height: 25px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.rdw-dropdownoption-highlighted {
|
||||||
|
background: #F1F1F1;
|
||||||
|
}
|
||||||
|
.rdw-dropdownoption-active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
.rdw-dropdownoption-disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-inline-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-inline-dropdown {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
.rdw-inline-dropdownoption {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-block-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-block-dropdown {
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-fontsize-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-fontsize-dropdown {
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
|
.rdw-fontsize-option {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-fontfamily-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-fontfamily-dropdown {
|
||||||
|
width: 115px;
|
||||||
|
}
|
||||||
|
.rdw-fontfamily-placeholder {
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 90px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.rdw-fontfamily-optionwrapper {
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-list-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-list-dropdown {
|
||||||
|
width: 50px;
|
||||||
|
z-index: 90;
|
||||||
|
}
|
||||||
|
.rdw-list-dropdownOption {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-text-align-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-text-align-dropdown {
|
||||||
|
width: 50px;
|
||||||
|
z-index: 90;
|
||||||
|
}
|
||||||
|
.rdw-text-align-dropdownOption {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.rdw-right-aligned-block {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.rdw-left-aligned-block {
|
||||||
|
text-align: left !important;
|
||||||
|
}
|
||||||
|
.rdw-center-aligned-block {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
.rdw-justify-aligned-block {
|
||||||
|
text-align: justify !important;
|
||||||
|
}
|
||||||
|
.rdw-right-aligned-block > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.rdw-left-aligned-block > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.rdw-center-aligned-block > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.rdw-justify-aligned-block > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-colorpicker-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-modal {
|
||||||
|
position: absolute;
|
||||||
|
top: 35px;
|
||||||
|
left: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 175px;
|
||||||
|
height: 175px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 3px 3px 5px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-modal-header {
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-modal-style-label {
|
||||||
|
font-size: 15px;
|
||||||
|
width: 50%;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 10px 5px;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-modal-style-label-active {
|
||||||
|
border-bottom: 2px solid #0a66b7;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-modal-options {
|
||||||
|
margin: 5px auto;
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-cube {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-option {
|
||||||
|
margin: 3px;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 20px;
|
||||||
|
border: none;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
min-width: 22px;
|
||||||
|
box-shadow: 1px 2px 1px #BFBDBD inset;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-option:hover {
|
||||||
|
box-shadow: 1px 2px 1px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-option:active {
|
||||||
|
box-shadow: -1px -2px 1px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-colorpicker-option-active {
|
||||||
|
box-shadow: 0px 0px 2px 2px #BFBDBD;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-link-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-link-dropdown {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
.rdw-link-dropdownOption {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.rdw-link-dropdownPlaceholder {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
.rdw-link-modal {
|
||||||
|
position: absolute;
|
||||||
|
top: 35px;
|
||||||
|
left: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 235px;
|
||||||
|
height: 205px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 3px 3px 5px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-label {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-input {
|
||||||
|
margin-top: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
height: 25px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-buttonsection {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-target-option {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-target-option > span {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
width: 75px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: white;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-btn:hover {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-btn:active {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD inset;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-btn:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
.rdw-link-modal-btn:disabled {
|
||||||
|
background: #ece9e9;
|
||||||
|
}
|
||||||
|
.rdw-link-dropdownoption {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.rdw-history-dropdown {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-embedded-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal {
|
||||||
|
position: absolute;
|
||||||
|
top: 35px;
|
||||||
|
left: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 235px;
|
||||||
|
height: 180px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-shadow: 3px 3px 5px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-header {
|
||||||
|
font-size: 15px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-header-option {
|
||||||
|
width: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-header-label {
|
||||||
|
width: 95px;
|
||||||
|
border: 1px solid #f1f1f1;
|
||||||
|
margin-top: 5px;
|
||||||
|
background: #6EB8D4;
|
||||||
|
border-bottom: 2px solid #0a66b7;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-link-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-link-input {
|
||||||
|
width: 88%;
|
||||||
|
height: 35px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-link-input-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-link-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-btn-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-btn {
|
||||||
|
margin: 0 3px;
|
||||||
|
width: 75px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: white;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-btn:hover {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-btn:active {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD inset;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-btn:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-btn:disabled {
|
||||||
|
background: #ece9e9;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-size {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin: 8px 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-size-input {
|
||||||
|
width: 80%;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.rdw-embedded-modal-size-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-emoji-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-emoji-modal {
|
||||||
|
overflow: auto;
|
||||||
|
position: absolute;
|
||||||
|
top: 35px;
|
||||||
|
left: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 235px;
|
||||||
|
height: 180px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 3px 3px 5px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-emoji-icon {
|
||||||
|
margin: 2.5px;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 22px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-spinner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.rdw-spinner > div {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background-color: #333;
|
||||||
|
|
||||||
|
border-radius: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||||
|
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
|
||||||
|
}
|
||||||
|
.rdw-spinner .rdw-bounce1 {
|
||||||
|
-webkit-animation-delay: -0.32s;
|
||||||
|
animation-delay: -0.32s;
|
||||||
|
}
|
||||||
|
.rdw-spinner .rdw-bounce2 {
|
||||||
|
-webkit-animation-delay: -0.16s;
|
||||||
|
animation-delay: -0.16s;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes sk-bouncedelay {
|
||||||
|
0%, 80%, 100% { -webkit-transform: scale(0) }
|
||||||
|
40% { -webkit-transform: scale(1.0) }
|
||||||
|
}
|
||||||
|
@keyframes sk-bouncedelay {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
-webkit-transform: scale(0);
|
||||||
|
transform: scale(0);
|
||||||
|
} 40% {
|
||||||
|
-webkit-transform: scale(1.0);
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-image-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-image-modal {
|
||||||
|
position: absolute;
|
||||||
|
top: 35px;
|
||||||
|
left: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 235px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 100;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 3px 3px 5px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-header {
|
||||||
|
font-size: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-header-option {
|
||||||
|
width: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-header-label {
|
||||||
|
width: 80px;
|
||||||
|
background: #f1f1f1;
|
||||||
|
border: 1px solid #f1f1f1;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-header-label-highlighted {
|
||||||
|
background: #6EB8D4;
|
||||||
|
border-bottom: 2px solid #0a66b7;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-upload-option {
|
||||||
|
width: 100%;
|
||||||
|
color: gray;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
border: none;
|
||||||
|
font-size: 15px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
outline: 2px dashed gray;
|
||||||
|
outline-offset: -10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 9px 0;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-upload-option-highlighted {
|
||||||
|
outline: 2px dashed #0a66b7;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-upload-option-label {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-upload-option-label span{
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-upload-option-image-preview {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-upload-option-input {
|
||||||
|
width: 0.1px;
|
||||||
|
height: 0.1px;
|
||||||
|
opacity: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-url-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-url-input {
|
||||||
|
width: 90%;
|
||||||
|
height: 35px;
|
||||||
|
margin: 15px 0 12px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-btn-section {
|
||||||
|
margin: 10px auto 0;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-url-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-btn {
|
||||||
|
margin: 0 5px;
|
||||||
|
width: 75px;
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: white;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-btn:hover {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-btn:active {
|
||||||
|
box-shadow: 1px 1px 0px #BFBDBD inset;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-btn:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-btn:disabled {
|
||||||
|
background: #ece9e9;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-spinner {
|
||||||
|
position: absolute;
|
||||||
|
top: -3px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-alt-input {
|
||||||
|
width: 70%;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-alt-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-alt-lbl {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-size {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
margin: 8px 0;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-size-input {
|
||||||
|
width: 40%;
|
||||||
|
height: 20px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.rdw-image-modal-size-input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.rdw-image-mandatory-sign {
|
||||||
|
color: red;
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-remove-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-history-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
flex-wrap: wrap
|
||||||
|
}
|
||||||
|
.rdw-history-dropdownoption {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.rdw-history-dropdown {
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-link-decorator-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.rdw-link-decorator-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 40%;
|
||||||
|
top: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-mention-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #1236ff;
|
||||||
|
background-color: #f0fbff;
|
||||||
|
padding: 1px 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-suggestion-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.rdw-suggestion-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
min-width: 100px;
|
||||||
|
max-height: 150px;
|
||||||
|
overflow: auto;
|
||||||
|
background: white;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.rdw-suggestion-option {
|
||||||
|
padding: 7px 5px;
|
||||||
|
border-bottom: 1px solid #f1f1f1;
|
||||||
|
}
|
||||||
|
.rdw-suggestion-option-active {
|
||||||
|
background-color: #F1F1F1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-hashtag-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #1236ff;
|
||||||
|
background-color: #f0fbff;
|
||||||
|
padding: 1px 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-image-alignment-options-popup {
|
||||||
|
position: absolute;
|
||||||
|
background: white;
|
||||||
|
display: flex;
|
||||||
|
padding: 5px 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
width: 105px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
.rdw-alignment-option-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
.rdw-image-alignment-option {
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
min-width: 15px;
|
||||||
|
}
|
||||||
|
.rdw-image-alignment {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.rdw-image-imagewrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.rdw-image-center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.rdw-image-left {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.rdw-image-right {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
.rdw-image-alignment-options-popup-right {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdw-editor-main {
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.rdw-editor-toolbar {
|
||||||
|
padding: 6px 5px 0;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: 1px solid #F1F1F1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
background: white;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
font-size: 15px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.public-DraftStyleDefault-block {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
.rdw-editor-wrapper:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.rdw-editor-wrapper {
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
.rdw-editor-main blockquote {
|
||||||
|
border-left: 5px solid #f1f1f1;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
.rdw-editor-main pre {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 1px 10px;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Draft v0.9.1
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*/
|
||||||
|
.DraftEditor-editorContainer,.DraftEditor-root,.public-DraftEditor-content{height:inherit;text-align:initial}.public-DraftEditor-content[contenteditable=true]{-webkit-user-modify:read-write-plaintext-only}.DraftEditor-root{position:relative}.DraftEditor-editorContainer{background-color:rgba(255,255,255,0);border-left:.1px solid transparent;position:relative;z-index:1}.public-DraftEditor-block{position:relative}.DraftEditor-alignLeft .public-DraftStyleDefault-block{text-align:left}.DraftEditor-alignLeft .public-DraftEditorPlaceholder-root{left:0;text-align:left}.DraftEditor-alignCenter .public-DraftStyleDefault-block{text-align:center}.DraftEditor-alignCenter .public-DraftEditorPlaceholder-root{margin:0 auto;text-align:center;width:100%}.DraftEditor-alignRight .public-DraftStyleDefault-block{text-align:right}.DraftEditor-alignRight .public-DraftEditorPlaceholder-root{right:0;text-align:right}.public-DraftEditorPlaceholder-root{color:#9197a3;position:absolute;z-index:0}.public-DraftEditorPlaceholder-hasFocus{color:#bdc1c9}.DraftEditorPlaceholder-hidden{display:none}.public-DraftStyleDefault-block{position:relative;white-space:pre-wrap}.public-DraftStyleDefault-ltr{direction:ltr;text-align:left}.public-DraftStyleDefault-rtl{direction:rtl;text-align:right}.public-DraftStyleDefault-listLTR{direction:ltr}.public-DraftStyleDefault-listRTL{direction:rtl}.public-DraftStyleDefault-ol,.public-DraftStyleDefault-ul{margin:16px 0;padding:0}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR{margin-left:1.5em}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-listRTL{margin-right:1.5em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listLTR{margin-left:3em}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-listRTL{margin-right:3em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listLTR{margin-left:4.5em}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-listRTL{margin-right:4.5em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listLTR{margin-left:6em}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-listRTL{margin-right:6em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listLTR{margin-left:7.5em}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-listRTL{margin-right:7.5em}.public-DraftStyleDefault-unorderedListItem{list-style-type:square;position:relative}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth0{list-style-type:disc}.public-DraftStyleDefault-unorderedListItem.public-DraftStyleDefault-depth1{list-style-type:circle}.public-DraftStyleDefault-orderedListItem{list-style-type:none;position:relative}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR:before{left:-36px;position:absolute;text-align:right;width:30px}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listRTL:before{position:absolute;right:-36px;text-align:left;width:30px}.public-DraftStyleDefault-orderedListItem:before{content:counter(ol0) ". ";counter-increment:ol0}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth1:before{content:counter(ol1) ". ";counter-increment:ol1}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth2:before{content:counter(ol2) ". ";counter-increment:ol2}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth3:before{content:counter(ol3) ". ";counter-increment:ol3}.public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-depth4:before{content:counter(ol4) ". ";counter-increment:ol4}.public-DraftStyleDefault-depth0.public-DraftStyleDefault-reset{counter-reset:ol0}.public-DraftStyleDefault-depth1.public-DraftStyleDefault-reset{counter-reset:ol1}.public-DraftStyleDefault-depth2.public-DraftStyleDefault-reset{counter-reset:ol2}.public-DraftStyleDefault-depth3.public-DraftStyleDefault-reset{counter-reset:ol3}.public-DraftStyleDefault-depth4.public-DraftStyleDefault-reset{counter-reset:ol4}
|
||||||
|
|
||||||
|
|
||||||
|
/*# sourceMappingURL=react-draft-wysiwyg.css.map*/
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/dashboard/build/index.js"></script>
|
<script type="module" src="/dashboard/build/index.js"></script>
|
||||||
|
<script>var global = global || window;</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
Reference in New Issue
Block a user