Merged in feature/new-hep-api (pull request #85)
Feature/new hep api Approved-by: Christian Cueni
This commit is contained in:
commit
7747a039cb
1
Pipfile
1
Pipfile
|
|
@ -44,3 +44,4 @@ unittest-xml-reporting = "*"
|
|||
django-silk = "*"
|
||||
wagtail-autocomplete = "*"
|
||||
jedi = "==0.17.2"
|
||||
Authlib = "*"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "58d8faf7e03679ac7b0053dd01e54288d3a719c8ee25c1edf20a74ebcbf87951"
|
||||
"sha256": "37e4b67556de5b9daa800e1078361cffdca6044d49ab74132616b10570a57acb"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
|
@ -23,6 +23,22 @@
|
|||
],
|
||||
"version": "==7.0.0"
|
||||
},
|
||||
"appnope": {
|
||||
"hashes": [
|
||||
"sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442",
|
||||
"sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"
|
||||
],
|
||||
"markers": "sys_platform == 'darwin'",
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"authlib": {
|
||||
"hashes": [
|
||||
"sha256:37df3a2554bc6fe0da3cc6848c44fac2ae40634a7f8fc72543947f4330b26464",
|
||||
"sha256:d9fe5edb59801b16583faa86f88d798d99d952979b9616d5c735b9170b41ae2c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.15.4"
|
||||
},
|
||||
"autopep8": {
|
||||
"hashes": [
|
||||
"sha256:276ced7e9e3cb22e5d7c14748384a5cf5d9002257c0ed50c0e075b68011bb6d0",
|
||||
|
|
@ -55,42 +71,110 @@
|
|||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:56f1766f1271b6b4e979c7b56225377f8912050e5935adc5c1c9e3a0338b949e",
|
||||
"sha256:c61c809d288e88b9a0d926f56f803d0128b498aa9b45a42a6e03cd9a83e5c124"
|
||||
"sha256:2c2f70608934b03f9c08f4cd185de223b5abd18245dd4d4800e1fbc2a2523e31",
|
||||
"sha256:fccfa81cda69bb2317ed97e7149d7d84d19e6ec3bfbe3f721139e7ac0c407c73"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.17.68"
|
||||
"version": "==1.17.98"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:0f693f5ad6348ec1a62b3a66fee2840d3b722d66b44896022d644275ff8b143d",
|
||||
"sha256:eb3544911cb0316a33b328a27d137130af278a9c0006be0c95e5e402b01d9865"
|
||||
"sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f",
|
||||
"sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.20.68"
|
||||
"version": "==1.20.98"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
|
||||
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
|
||||
],
|
||||
"version": "==2020.12.5"
|
||||
"version": "==2021.5.30"
|
||||
},
|
||||
"cffi": {
|
||||
"hashes": [
|
||||
"sha256:005a36f41773e148deac64b08f233873a4d0c18b053d37da83f6af4d9087b813",
|
||||
"sha256:04c468b622ed31d408fea2346bec5bbffba2cc44226302a0de1ade9f5ea3d373",
|
||||
"sha256:06d7cd1abac2ffd92e65c0609661866709b4b2d82dd15f611e602b9b188b0b69",
|
||||
"sha256:06db6321b7a68b2bd6df96d08a5adadc1fa0e8f419226e25b2a5fbf6ccc7350f",
|
||||
"sha256:0857f0ae312d855239a55c81ef453ee8fd24136eaba8e87a2eceba644c0d4c06",
|
||||
"sha256:0f861a89e0043afec2a51fd177a567005847973be86f709bbb044d7f42fc4e05",
|
||||
"sha256:1071534bbbf8cbb31b498d5d9db0f274f2f7a865adca4ae429e147ba40f73dea",
|
||||
"sha256:158d0d15119b4b7ff6b926536763dc0714313aa59e320ddf787502c70c4d4bee",
|
||||
"sha256:1bf1ac1984eaa7675ca8d5745a8cb87ef7abecb5592178406e55858d411eadc0",
|
||||
"sha256:1f436816fc868b098b0d63b8920de7d208c90a67212546d02f84fe78a9c26396",
|
||||
"sha256:24a570cd11895b60829e941f2613a4f79df1a27344cbbb82164ef2e0116f09c7",
|
||||
"sha256:24ec4ff2c5c0c8f9c6b87d5bb53555bf267e1e6f70e52e5a9740d32861d36b6f",
|
||||
"sha256:2894f2df484ff56d717bead0a5c2abb6b9d2bf26d6960c4604d5c48bbc30ee73",
|
||||
"sha256:29314480e958fd8aab22e4a58b355b629c59bf5f2ac2492b61e3dc06d8c7a315",
|
||||
"sha256:293e7ea41280cb28c6fcaaa0b1aa1f533b8ce060b9e701d78511e1e6c4a1de76",
|
||||
"sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1",
|
||||
"sha256:35f27e6eb43380fa080dccf676dece30bef72e4a67617ffda586641cd4508d49",
|
||||
"sha256:3c3f39fa737542161d8b0d680df2ec249334cd70a8f420f71c9304bd83c3cbed",
|
||||
"sha256:3d3dd4c9e559eb172ecf00a2a7517e97d1e96de2a5e610bd9b68cea3925b4892",
|
||||
"sha256:43e0b9d9e2c9e5d152946b9c5fe062c151614b262fda2e7b201204de0b99e482",
|
||||
"sha256:48e1c69bbacfc3d932221851b39d49e81567a4d4aac3b21258d9c24578280058",
|
||||
"sha256:51182f8927c5af975fece87b1b369f722c570fe169f9880764b1ee3bca8347b5",
|
||||
"sha256:58e3f59d583d413809d60779492342801d6e82fefb89c86a38e040c16883be53",
|
||||
"sha256:5de7970188bb46b7bf9858eb6890aad302577a5f6f75091fd7cdd3ef13ef3045",
|
||||
"sha256:65fa59693c62cf06e45ddbb822165394a288edce9e276647f0046e1ec26920f3",
|
||||
"sha256:681d07b0d1e3c462dd15585ef5e33cb021321588bebd910124ef4f4fb71aef55",
|
||||
"sha256:69e395c24fc60aad6bb4fa7e583698ea6cc684648e1ffb7fe85e3c1ca131a7d5",
|
||||
"sha256:6c97d7350133666fbb5cf4abdc1178c812cb205dc6f41d174a7b0f18fb93337e",
|
||||
"sha256:6e4714cc64f474e4d6e37cfff31a814b509a35cb17de4fb1999907575684479c",
|
||||
"sha256:72d8d3ef52c208ee1c7b2e341f7d71c6fd3157138abf1a95166e6165dd5d4369",
|
||||
"sha256:8ae6299f6c68de06f136f1f9e69458eae58f1dacf10af5c17353eae03aa0d827",
|
||||
"sha256:8b198cec6c72df5289c05b05b8b0969819783f9418e0409865dac47288d2a053",
|
||||
"sha256:99cd03ae7988a93dd00bcd9d0b75e1f6c426063d6f03d2f90b89e29b25b82dfa",
|
||||
"sha256:9cf8022fb8d07a97c178b02327b284521c7708d7c71a9c9c355c178ac4bbd3d4",
|
||||
"sha256:9de2e279153a443c656f2defd67769e6d1e4163952b3c622dcea5b08a6405322",
|
||||
"sha256:9e93e79c2551ff263400e1e4be085a1210e12073a31c2011dbbda14bda0c6132",
|
||||
"sha256:9ff227395193126d82e60319a673a037d5de84633f11279e336f9c0f189ecc62",
|
||||
"sha256:a465da611f6fa124963b91bf432d960a555563efe4ed1cc403ba5077b15370aa",
|
||||
"sha256:ad17025d226ee5beec591b52800c11680fca3df50b8b29fe51d882576e039ee0",
|
||||
"sha256:afb29c1ba2e5a3736f1c301d9d0abe3ec8b86957d04ddfa9d7a6a42b9367e396",
|
||||
"sha256:b85eb46a81787c50650f2392b9b4ef23e1f126313b9e0e9013b35c15e4288e2e",
|
||||
"sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991",
|
||||
"sha256:cbde590d4faaa07c72bf979734738f328d239913ba3e043b1e98fe9a39f8b2b6",
|
||||
"sha256:cc5a8e069b9ebfa22e26d0e6b97d6f9781302fe7f4f2b8776c3e1daea35f1adc",
|
||||
"sha256:cd2868886d547469123fadc46eac7ea5253ea7fcb139f12e1dfc2bbd406427d1",
|
||||
"sha256:d42b11d692e11b6634f7613ad8df5d6d5f8875f5d48939520d351007b3c13406",
|
||||
"sha256:df5052c5d867c1ea0b311fb7c3cd28b19df469c056f7fdcfe88c7473aa63e333",
|
||||
"sha256:f2d45f97ab6bb54753eab54fffe75aaf3de4ff2341c9daee1987ee1837636f1d",
|
||||
"sha256:fd78e5fee591709f32ef6edb9a015b4aa1a5022598e36227500c8f4e02328d9c"
|
||||
],
|
||||
"version": "==1.14.5"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"cryptography": {
|
||||
"hashes": [
|
||||
"sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
|
||||
"sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959",
|
||||
"sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6",
|
||||
"sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873",
|
||||
"sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2",
|
||||
"sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713",
|
||||
"sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1",
|
||||
"sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177",
|
||||
"sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250",
|
||||
"sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca",
|
||||
"sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
|
||||
"sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
|
||||
],
|
||||
"version": "==3.4.7"
|
||||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060",
|
||||
"sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"
|
||||
"sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323",
|
||||
"sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==5.0.7"
|
||||
"version": "==5.0.9"
|
||||
},
|
||||
"dj-database-url": {
|
||||
"hashes": [
|
||||
|
|
@ -101,11 +185,11 @@
|
|||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:db2214db1c99017cbd971e58824e6f424375154fe358afc30e976f5b99fc6060",
|
||||
"sha256:e831105edb153af1324de44d06091ca75520a227456387dda4a47d2f1cc2731a"
|
||||
"sha256:3339ff0e03dee13045aef6ae7b523edff75b6d726adf7a7a48f53d5a501f7db7",
|
||||
"sha256:f2084ceecff86b1e631c2cd4107d435daf4e12f1efcdf11061a73bf0b5e95f92"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.22"
|
||||
"version": "==2.2.24"
|
||||
},
|
||||
"django-appconf": {
|
||||
"hashes": [
|
||||
|
|
@ -189,7 +273,6 @@
|
|||
"sha256:710b4d15ec1996550cc68a0abbc41903ca7d832540e52b1336e6858737e410d8",
|
||||
"sha256:bb8f27684814cd1414b2af75b857b5e26a40912631904038a7ecacd2bfafc3ac"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.24.0"
|
||||
},
|
||||
"django-treebeard": {
|
||||
|
|
@ -197,7 +280,6 @@
|
|||
"sha256:7c2b1cdb1e9b46d595825186064a1228bc4d00dbbc186db5b0b9412357fba91c",
|
||||
"sha256:80150017725239702054e5fa64dc66e383dc13ac262c8d47ee5a82cb005969da"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==4.5.1"
|
||||
},
|
||||
"djangorestframework": {
|
||||
|
|
@ -225,17 +307,15 @@
|
|||
},
|
||||
"faker": {
|
||||
"hashes": [
|
||||
"sha256:156854f36d4086bb21ff85a79b4d6a6403a240cd2c17a33a44b8ea4ff4e957c2",
|
||||
"sha256:a2ed065342e91a7672407325848cd5728d5e5eb4928d0a1c478fd4f0dd97d1f7"
|
||||
"sha256:ccd76cd86a49f1042811faaa3a7d1b094fcf8e60a1ec286190417bbb5a3f2f76",
|
||||
"sha256:cda50f6afaa4075464d7500ac838ec3cac3cc6824297e4340b2a17a62dc086a8"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==8.1.2"
|
||||
"version": "==8.8.1"
|
||||
},
|
||||
"future": {
|
||||
"hashes": [
|
||||
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.18.2"
|
||||
},
|
||||
"gprof2dot": {
|
||||
|
|
@ -286,7 +366,6 @@
|
|||
"sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d",
|
||||
"sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==1.1"
|
||||
},
|
||||
"idna": {
|
||||
|
|
@ -294,16 +373,15 @@
|
|||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:714810a5c74f512b69d5f3b944c86e592cee0a5fb9c728e582f074610f6cf038",
|
||||
"sha256:f78c6a3972dde1cc9e4041cbf4de583546314ba52d3c97208e5b6b2221a9cb7d"
|
||||
"sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64",
|
||||
"sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.23.1"
|
||||
"version": "==7.16.1"
|
||||
},
|
||||
"ipython-genutils": {
|
||||
"hashes": [
|
||||
|
|
@ -322,127 +400,90 @@
|
|||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
|
||||
"sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
|
||||
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
|
||||
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.11.3"
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
|
||||
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"libsass": {
|
||||
"hashes": [
|
||||
"sha256:1521d2a8d4b397c6ec90640a1f6b5529077035efc48ef1c2e53095544e713d1b",
|
||||
"sha256:1b2d415bbf6fa7da33ef46e549db1418498267b459978eff8357e5e823962d35",
|
||||
"sha256:25ebc2085f5eee574761ccc8d9cd29a9b436fc970546d5ef08c6fa41eb57dff1",
|
||||
"sha256:2ae806427b28bc1bb7cb0258666d854fcf92ba52a04656b0b17ba5e190fb48a9",
|
||||
"sha256:4a246e4b88fd279abef8b669206228c92534d96ddcd0770d7012088c408dff23",
|
||||
"sha256:553e5096414a8d4fb48d0a48f5a038d3411abe254d79deac5e008516c019e63a",
|
||||
"sha256:697f0f9fa8a1367ca9ec6869437cb235b1c537fc8519983d1d890178614a8903",
|
||||
"sha256:a8fd4af9f853e8bf42b1425c5e48dd90b504fa2e70d7dac5ac80b8c0a5a5fe85",
|
||||
"sha256:c9411fec76f480ffbacc97d8188322e02a5abca6fc78e70b86a2a2b421eae8a2",
|
||||
"sha256:daa98a51086d92aa7e9c8871cf1a8258124b90e2abf4697852a3dca619838618",
|
||||
"sha256:e0e60836eccbf2d9e24ec978a805cd6642fa92515fbd95e3493fee276af76f8a",
|
||||
"sha256:e64ae2587f1a683e831409aad03ba547c245ef997e1329fffadf7a866d2510b8",
|
||||
"sha256:f6852828e9e104d2ce0358b73c550d26dd86cc3a69439438c3b618811b9584f5"
|
||||
"sha256:06c8776417fe930714bdc930a3d7e795ae3d72be6ac883ff72a1b8f7c49e5ffb",
|
||||
"sha256:12f39712de38689a8b785b7db41d3ba2ea1d46f9379d81ea4595802d91fa6529",
|
||||
"sha256:1e25dd9047a9392d3c59a0b869e0404f2b325a03871ee45285ee33b3664f5613",
|
||||
"sha256:659ae41af8708681fa3ec73f47b9735a6725e71c3b66ff570bfce78952f2314e",
|
||||
"sha256:6b984510ed94993708c0d697b4fef2d118929bbfffc3b90037be0f5ccadf55e7",
|
||||
"sha256:a005f298f64624f313a3ac618ab03f844c71d84ae4f4a4aec4b68d2a4ffe75eb",
|
||||
"sha256:abc29357ee540849faf1383e1746d40d69ed5cb6d4c346df276b258f5aa8977a",
|
||||
"sha256:d5ba529d9ce668be9380563279f3ffe988f27bc5b299c5a28453df2e0b0fbaf2",
|
||||
"sha256:e2b1a7d093f2e76dc694c17c0c285e846d0b0deb0e8b21dc852ba1a3a4e2f1d6"
|
||||
],
|
||||
"version": "==0.20.1"
|
||||
"version": "==0.21.0"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||
"sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
|
||||
"sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
|
||||
"sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
|
||||
"sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
|
||||
"sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
|
||||
"sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||
"sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
|
||||
"sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
|
||||
"sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
|
||||
"sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
|
||||
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
|
||||
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
|
||||
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
|
||||
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
|
||||
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
|
||||
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
|
||||
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
|
||||
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
|
||||
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
|
||||
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
|
||||
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
|
||||
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
|
||||
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
|
||||
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
|
||||
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
|
||||
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
|
||||
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
|
||||
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
|
||||
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
|
||||
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
|
||||
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
|
||||
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
|
||||
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
|
||||
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
|
||||
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
|
||||
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
|
||||
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
|
||||
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
|
||||
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
|
||||
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
|
||||
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
|
||||
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
|
||||
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
||||
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"matplotlib-inline": {
|
||||
"hashes": [
|
||||
"sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811",
|
||||
"sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.1.2"
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"newrelic": {
|
||||
"hashes": [
|
||||
"sha256:242a5e901d684f7ffdd621bc58da8fe9a85d5545b4b63e1070589f5ab45c9e1e",
|
||||
"sha256:3dec4647de67609570c4e305f2b6432a00e0a0940a7ac69660ee92268b49d6e7",
|
||||
"sha256:489e5a450aae1a5ecf7ca488739bd274296b19049bb7927c7ef71953ad5ad437",
|
||||
"sha256:503e5dfcbf215fe68e4349ea452b5b00234010122ca72d80b64c73270654916c",
|
||||
"sha256:5b0a04f7acf4dafe8d3935ac8688143bc0a0c61e15e2a779b152afcc3c88ee45",
|
||||
"sha256:6a87cd6102aba7c9619a6e4b9e1aa6322ef81367b1a8f24ad996a07333313c8c",
|
||||
"sha256:90d2bab0a08001d84499bf11c62c49d1fc6f2835c05d12994b5a931cad48f120",
|
||||
"sha256:a3b928a052be318cb0cdb56977c630f1ded1d8e391c876ed5ae4442aa7ad499a",
|
||||
"sha256:adc748f633bd64e295b403448daa8416961b0f99af6787b857009d737c5e8af3",
|
||||
"sha256:e767af29572a9457a5e5f13481fe735c1a9ae2d1683b7b35c8757f9df275a538",
|
||||
"sha256:fede816248d0a1e5e11487ecc122f24c9d33e08a6ac1f882044ac6f3b2c90ae0"
|
||||
"sha256:08a518af90505750ef2687411c0de7db438cc7b29e6d3855b27a7aae068b67b8",
|
||||
"sha256:6244e4fff580157e5ab7f7879d188f8bc6c8760b180a46625ba00c8f99f56e2a",
|
||||
"sha256:8a42ed37ba95dfc1a82bd70de6d8311d549fa2bad45229a98c24f33ef50e3616",
|
||||
"sha256:9ee632b9418ae61a64523223e621004041bbd5be7c80d8b01d46a66f4989f213",
|
||||
"sha256:beac3552c6e78de4a99320937c0e675fb3cc5cbe24ab7156964695312841ddc6",
|
||||
"sha256:c1575705d0f2c19cbef002504255e5da0ca5905dc5df02da71ebcb4e032de361",
|
||||
"sha256:c8fa74b8dba90e07dfa72e2b88e27b41091964c9843d9f411cb66c59cad90994",
|
||||
"sha256:ea43ee0cd3e19de074e17c47029f3965b486587fe95dfcc2ea4be1b7d0adb58c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==6.2.0.156"
|
||||
"version": "==6.4.2.159"
|
||||
},
|
||||
"packaging": {
|
||||
"hashes": [
|
||||
"sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
|
||||
"sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==20.9"
|
||||
},
|
||||
"parso": {
|
||||
|
|
@ -450,7 +491,6 @@
|
|||
"sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea",
|
||||
"sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"pexpect": {
|
||||
|
|
@ -473,30 +513,19 @@
|
|||
"sha256:0013f590a8f260df60bcfd65db19d18efc04e7f046c3c82a40e2e2b3292a937c",
|
||||
"sha256:0b899ee80920bb533f26581af9b4660bc12aff4562555afe74e429101ebf3c94",
|
||||
"sha256:12f29d6c23424f704c66b5b68c02fe0b571504459605cfe36ab8158359b0e1bb",
|
||||
"sha256:135e9aa65150c53f7db85bf2bebb8a0e1a48ea850e80cf66e16dd04fa09d309c",
|
||||
"sha256:153ec6f18f7b61641e0e6e502acfaf4a06c9aba2ea11c0b4b3578ea9f13a4a4a",
|
||||
"sha256:17fe25efc785194d48c38fad85dce470013ba19d2fb66639e149f14bccf1327f",
|
||||
"sha256:1912b7230459fd53682dae32b83cbd8e5d642ba36d4be18566f00a9c063aa13d",
|
||||
"sha256:1a5b93084e01328a1cb1ecdad99d11d75e881e89a95f88d85b523646553b36c2",
|
||||
"sha256:25193f934d37d836a6b1f4c062ce574a96cbca7c6d9dc8ddfbbac7f9c54deaa4",
|
||||
"sha256:2c042352b430d678db50c78c5214e19638eff8b688941271da2de21fd298dfe5",
|
||||
"sha256:2e818dbe445e86fc6c266973fe540c35125c42eb2cf13a6095e9adaa89c0deb5",
|
||||
"sha256:2fcde9954c8882d1c7f93bb828caa34a4c5e3ee69dbc7895dc8652ad972b455a",
|
||||
"sha256:35f7d998b8e82fb3fb51ff88b30485eb81cd7dd56ec7e1a8deba23eb88532d44",
|
||||
"sha256:37cc0339abfa9e295c75d9a7f227d35cb44716feb95057f9449c4a9e9a17daf7",
|
||||
"sha256:43334f9581cd067945b8898cef9eb5714ee4883f8de0304c011f1dbdb1d4e2aa",
|
||||
"sha256:4bd4a71501b6d51db4abc07e1f43f5a6fed0a1a9583cca0b401d6af50284b0db",
|
||||
"sha256:57aa6198ba8acba1313c3b743e267d821a60cac77e6026caf0b55ca58d3d23be",
|
||||
"sha256:5b0d657460d9f3615876fec6306e97ca15a471f6169b622d76a47e270998acf1",
|
||||
"sha256:5cd36804f9f06a914a883fe682df5711d16d7b4f44d43189c5f013e7cd91e149",
|
||||
"sha256:6977cf073d83358b34f93abf5c1f1193b88675fe0e4441e0e28318bc3dcba7a0",
|
||||
"sha256:718ec7a122b28d64afc5fbc3a9b99bb0545ef511373cac06fe7624520e82cb20",
|
||||
"sha256:7dfbefdb3fb911ca9faed307bf309861e9995e36cca6b761c7ba6d9b77a9744a",
|
||||
"sha256:801cca8923508311bf5d6d0f7da5362552e8208ebd8ec0d7b9f2cd2ff5705734",
|
||||
"sha256:82b172e3264e62372c01b5b009b5b1a02fbb9276cbe5cc57ab00a6d6e5ed9a18",
|
||||
"sha256:82d1ff571489765df2816785d532e243bde213752156c227fca595723ec5ff42",
|
||||
"sha256:8580fc58074a16b749905b26cf8363f7b628dd167ba0130f5382cdc91c86b509",
|
||||
"sha256:931030d1d6282b7900e6b0a7ff9ecdb503b5e1e6781800dab2b71a9f39405bff",
|
||||
"sha256:9525cd680a6f9e80c6c0af03cf973e6505c59f60b4745f682cd1a449e54b31bb",
|
||||
"sha256:a224651a81e45ef4f1d0164e256c5f6b4abb49f2ae8f22ba2f3a9d0ff338e608",
|
||||
"sha256:a370d1c570f1d72e877099651e752332444b1c5009381f043c9da5fd47f3ebae",
|
||||
|
|
@ -505,12 +534,7 @@
|
|||
"sha256:b85f703c2ffe539313e39ce0676bed0f355cec45a16e58c9ab7417445843047c",
|
||||
"sha256:b9f63451084a718eccdeb1e382768c94647915653af4d6019f64560d9e98642b",
|
||||
"sha256:c793dfaa130847ccff958492b76ae8b9304e60b8a79a92962cb19e368276a22b",
|
||||
"sha256:d60c1625b108432ace8b1fa1a584017e5efa73f107d0f493c7f39c79bebf1d41",
|
||||
"sha256:dc4b018d5c9b636f7546583c5591b9ea00c328c3e5871992ef5b95bac353f097",
|
||||
"sha256:ddd16ab250b4fc97db1c47407e78c25216a75c29d29d10ad37e51b7a2ec7b2c3",
|
||||
"sha256:e126ff4fed71e78333840c07279e1617f63cfca76d63ad5b27d65a7277206a3d",
|
||||
"sha256:f8d49be8c282df8d2e1ab6ab53ab8abd859b1fa6fed384457ee85c9eff64ef97",
|
||||
"sha256:fcf64c91fd44485100a2965d23bb0e227d093e91f7e776c5ca3b32574766eb56"
|
||||
"sha256:ddd16ab250b4fc97db1c47407e78c25216a75c29d29d10ad37e51b7a2ec7b2c3"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==5.0.0"
|
||||
|
|
@ -523,11 +547,10 @@
|
|||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04",
|
||||
"sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"
|
||||
"sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
|
||||
"sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.0.18"
|
||||
"version": "==3.0.19"
|
||||
},
|
||||
"psycopg2": {
|
||||
"hashes": [
|
||||
|
|
@ -562,15 +585,20 @@
|
|||
"sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068",
|
||||
"sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.7.0"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
|
||||
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
|
||||
],
|
||||
"version": "==2.20"
|
||||
},
|
||||
"pygments": {
|
||||
"hashes": [
|
||||
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
|
||||
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.9.0"
|
||||
},
|
||||
"pyparsing": {
|
||||
|
|
@ -578,7 +606,6 @@
|
|||
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
|
||||
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.4.7"
|
||||
},
|
||||
"python-dateutil": {
|
||||
|
|
@ -586,7 +613,6 @@
|
|||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"python-dotenv": {
|
||||
|
|
@ -669,11 +695,10 @@
|
|||
},
|
||||
"sendgrid": {
|
||||
"hashes": [
|
||||
"sha256:273bdc0abec649bf6319df7b6267980f79e53ab64e92906d65eea6d4330d00b4",
|
||||
"sha256:74b0dcf9a79188948f61f456bd1bf67ffa676a5d388aba1c76bff516566d7084"
|
||||
"sha256:1c1cca97ab968f81af43ddbbe44aade5a689da27e3e4975dc366042499620abe",
|
||||
"sha256:2558a8b2cf12677ceb99f8b611d914af5b9a2fd7ff3c0578e8299b4224e10071"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==6.7.0"
|
||||
"version": "==6.7.1"
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"hashes": [
|
||||
|
|
@ -685,18 +710,16 @@
|
|||
},
|
||||
"singledispatch": {
|
||||
"hashes": [
|
||||
"sha256:58b46ce1cc4d43af0aac3ac9a047bdb0f44e05f0b2fa2eec755863331700c865",
|
||||
"sha256:85c97f94c8957fa4e6dab113156c182fb346d56d059af78aad710bced15f16fb"
|
||||
"sha256:0d428477703d8386eb6aeed6e522c9f22d49f4363cdf4ed6a2ba3dc276053e20",
|
||||
"sha256:d5bb9405a4b8de48e36709238e8b91b4f6f300f81a5132ba2531a9a738eca391"
|
||||
],
|
||||
"markers": "python_version >= '2.6'",
|
||||
"version": "==3.6.1"
|
||||
"version": "==3.6.2"
|
||||
},
|
||||
"six": {
|
||||
"hashes": [
|
||||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
|
|
@ -704,14 +727,13 @@
|
|||
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
||||
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"starkbank-ecdsa": {
|
||||
"hashes": [
|
||||
"sha256:423f81bb55c896a3c85ee98ac7da98826721eaee918f5c0c1dfff99e1972da0c"
|
||||
"sha256:f7b434b4a1e0ba082fb1804b908b79523973fd17b1fde377078857f7cee299d1"
|
||||
],
|
||||
"version": "==1.1.0"
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"text-unidecode": {
|
||||
"hashes": [
|
||||
|
|
@ -725,24 +747,14 @@
|
|||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"traitlets": {
|
||||
"hashes": [
|
||||
"sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396",
|
||||
"sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"
|
||||
"sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
|
||||
"sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==5.0.5"
|
||||
},
|
||||
"typing": {
|
||||
"hashes": [
|
||||
"sha256:1187fb9c82fd670d10aa07bbb6cfcfe4bdda42d6fab8d5134f04e8c4d0b71cc9",
|
||||
"sha256:283d868f5071ab9ad873e5e52268d611e851c870a2ba354193026f2dfb29d8b5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==3.7.4.3"
|
||||
"version": "==4.3.3"
|
||||
},
|
||||
"unidecode": {
|
||||
"hashes": [
|
||||
|
|
@ -761,11 +773,10 @@
|
|||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
|
||||
"sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
|
||||
"sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c",
|
||||
"sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.4"
|
||||
"version": "==1.26.5"
|
||||
},
|
||||
"wagtail": {
|
||||
"hashes": [
|
||||
|
|
@ -821,12 +832,19 @@
|
|||
}
|
||||
},
|
||||
"develop": {
|
||||
"appnope": {
|
||||
"hashes": [
|
||||
"sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442",
|
||||
"sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"
|
||||
],
|
||||
"markers": "sys_platform == 'darwin'",
|
||||
"version": "==0.1.2"
|
||||
},
|
||||
"asgiref": {
|
||||
"hashes": [
|
||||
"sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee",
|
||||
"sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==3.3.4"
|
||||
},
|
||||
"autopep8": {
|
||||
|
|
@ -838,11 +856,11 @@
|
|||
},
|
||||
"awscli": {
|
||||
"hashes": [
|
||||
"sha256:57ae60a3f59cac265a9e5321c618b8768fdee89565089ada271e24489be5110d",
|
||||
"sha256:a26b5e24f70cb2c542128ccc11e9d38e43cded687d60cd2ca18b3d28cd902509"
|
||||
"sha256:114a945cb80677907cfa81cfa436cfe11d679303e6276bdf851294377e741045",
|
||||
"sha256:bcdcd790008547eedf46730558aa6e355e1c6c66f997d42fc7ce1329a1b1363c"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.19.68"
|
||||
"version": "==1.19.98"
|
||||
},
|
||||
"backcall": {
|
||||
"hashes": [
|
||||
|
|
@ -853,25 +871,23 @@
|
|||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:0f693f5ad6348ec1a62b3a66fee2840d3b722d66b44896022d644275ff8b143d",
|
||||
"sha256:eb3544911cb0316a33b328a27d137130af278a9c0006be0c95e5e402b01d9865"
|
||||
"sha256:b2a49de4ee04b690142c8e7240f0f5758e3f7673dd39cf398efe893bf5e11c3f",
|
||||
"sha256:b955b23fe2fbdbbc8e66f37fe2970de6b5d8169f940b200bcf434751709d38f6"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==1.20.68"
|
||||
"version": "==1.20.98"
|
||||
},
|
||||
"certifi": {
|
||||
"hashes": [
|
||||
"sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
|
||||
"sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
|
||||
"sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
|
||||
"sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
|
||||
],
|
||||
"version": "==2020.12.5"
|
||||
"version": "==2021.5.30"
|
||||
},
|
||||
"chardet": {
|
||||
"hashes": [
|
||||
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
|
||||
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==4.0.0"
|
||||
},
|
||||
"colorama": {
|
||||
|
|
@ -879,7 +895,6 @@
|
|||
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
|
||||
"sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==0.4.3"
|
||||
},
|
||||
"coverage": {
|
||||
|
|
@ -942,19 +957,18 @@
|
|||
},
|
||||
"decorator": {
|
||||
"hashes": [
|
||||
"sha256:6f201a6c4dac3d187352661f508b9364ec8091217442c9478f1f83c003a0f060",
|
||||
"sha256:945d84890bb20cc4a2f4a31fc4311c0c473af65ea318617f13a7257c9a58bc98"
|
||||
"sha256:6e5c199c16f7a9f0e3a61a4a54b3d27e7dad0dbdde92b944426cb20914376323",
|
||||
"sha256:72ecfba4320a893c53f9706bebb2d55c270c1e51a28789361aa93e4a21319ed5"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==5.0.7"
|
||||
"version": "==5.0.9"
|
||||
},
|
||||
"django": {
|
||||
"hashes": [
|
||||
"sha256:db2214db1c99017cbd971e58824e6f424375154fe358afc30e976f5b99fc6060",
|
||||
"sha256:e831105edb153af1324de44d06091ca75520a227456387dda4a47d2f1cc2731a"
|
||||
"sha256:3339ff0e03dee13045aef6ae7b523edff75b6d726adf7a7a48f53d5a501f7db7",
|
||||
"sha256:f2084ceecff86b1e631c2cd4107d435daf4e12f1efcdf11061a73bf0b5e95f92"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.2.22"
|
||||
"version": "==2.2.24"
|
||||
},
|
||||
"django-silk": {
|
||||
"hashes": [
|
||||
|
|
@ -969,7 +983,6 @@
|
|||
"sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827",
|
||||
"sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.15.2"
|
||||
},
|
||||
"gprof2dot": {
|
||||
|
|
@ -983,23 +996,22 @@
|
|||
"sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
|
||||
"sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.10"
|
||||
},
|
||||
"ipdb": {
|
||||
"hashes": [
|
||||
"sha256:178c367a61c1039e44e17c56fcc4a6e7dc11b33561261382d419b6ddb4401810"
|
||||
"sha256:951bd9a64731c444fd907a5ce268543020086a697f6be08f7cc2c9a752a278c5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.13.7"
|
||||
"version": "==0.13.9"
|
||||
},
|
||||
"ipython": {
|
||||
"hashes": [
|
||||
"sha256:714810a5c74f512b69d5f3b944c86e592cee0a5fb9c728e582f074610f6cf038",
|
||||
"sha256:f78c6a3972dde1cc9e4041cbf4de583546314ba52d3c97208e5b6b2221a9cb7d"
|
||||
"sha256:2dbcc8c27ca7d3cfe4fcdff7f45b27f9a8d3edfa70ff8024a71c7a8eb5f09d64",
|
||||
"sha256:9f4fcb31d3b2c533333893b9172264e4821c1ac91839500f31bd43f2c59b3ccf"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.23.1"
|
||||
"version": "==7.16.1"
|
||||
},
|
||||
"ipython-genutils": {
|
||||
"hashes": [
|
||||
|
|
@ -1018,92 +1030,62 @@
|
|||
},
|
||||
"jinja2": {
|
||||
"hashes": [
|
||||
"sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
|
||||
"sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
|
||||
"sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4",
|
||||
"sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
||||
"version": "==2.11.3"
|
||||
"version": "==3.0.1"
|
||||
},
|
||||
"jmespath": {
|
||||
"hashes": [
|
||||
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
|
||||
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.0"
|
||||
},
|
||||
"markupsafe": {
|
||||
"hashes": [
|
||||
"sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
|
||||
"sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
|
||||
"sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
|
||||
"sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
|
||||
"sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
|
||||
"sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
|
||||
"sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
|
||||
"sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
|
||||
"sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
|
||||
"sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
|
||||
"sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
|
||||
"sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
|
||||
"sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
|
||||
"sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
|
||||
"sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
|
||||
"sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
|
||||
"sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
|
||||
"sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
|
||||
"sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
|
||||
"sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
|
||||
"sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
|
||||
"sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
|
||||
"sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
|
||||
"sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
|
||||
"sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
|
||||
"sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
|
||||
"sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
|
||||
"sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
|
||||
"sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
|
||||
"sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
|
||||
"sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
|
||||
"sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
|
||||
"sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
|
||||
"sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
|
||||
"sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
|
||||
"sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
|
||||
"sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
|
||||
"sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
|
||||
"sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
|
||||
"sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
|
||||
"sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
|
||||
"sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
|
||||
"sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
|
||||
"sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
|
||||
"sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
|
||||
"sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
|
||||
"sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
|
||||
"sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
|
||||
"sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
|
||||
"sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
|
||||
"sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
|
||||
"sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
|
||||
"sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298",
|
||||
"sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64",
|
||||
"sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b",
|
||||
"sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567",
|
||||
"sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff",
|
||||
"sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74",
|
||||
"sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35",
|
||||
"sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26",
|
||||
"sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7",
|
||||
"sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75",
|
||||
"sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f",
|
||||
"sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135",
|
||||
"sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8",
|
||||
"sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a",
|
||||
"sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914",
|
||||
"sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18",
|
||||
"sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8",
|
||||
"sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2",
|
||||
"sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d",
|
||||
"sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b",
|
||||
"sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f",
|
||||
"sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb",
|
||||
"sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833",
|
||||
"sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415",
|
||||
"sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902",
|
||||
"sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9",
|
||||
"sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d",
|
||||
"sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066",
|
||||
"sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f",
|
||||
"sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5",
|
||||
"sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94",
|
||||
"sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509",
|
||||
"sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51",
|
||||
"sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.1.1"
|
||||
},
|
||||
"matplotlib-inline": {
|
||||
"hashes": [
|
||||
"sha256:5cf1176f554abb4fa98cb362aa2b55c500147e4bdbb07e3fda359143e1da0811",
|
||||
"sha256:f41d5ff73c9f5385775d5c0bc13b424535c8402fe70ea8210f93e11f3683993e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.1.2"
|
||||
"version": "==2.0.1"
|
||||
},
|
||||
"parso": {
|
||||
"hashes": [
|
||||
"sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea",
|
||||
"sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.7.1"
|
||||
},
|
||||
"pexpect": {
|
||||
|
|
@ -1123,11 +1105,10 @@
|
|||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04",
|
||||
"sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"
|
||||
"sha256:08360ee3a3148bdb5163621709ee322ec34fc4375099afa4bbf751e9b7b7fa4f",
|
||||
"sha256:7089d8d2938043508aa9420ec18ce0922885304cddae87fb96eebca942299f88"
|
||||
],
|
||||
"markers": "python_full_version >= '3.6.1'",
|
||||
"version": "==3.0.18"
|
||||
"version": "==3.0.19"
|
||||
},
|
||||
"ptyprocess": {
|
||||
"hashes": [
|
||||
|
|
@ -1138,19 +1119,8 @@
|
|||
},
|
||||
"pyasn1": {
|
||||
"hashes": [
|
||||
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
|
||||
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
|
||||
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
|
||||
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
|
||||
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
|
||||
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
|
||||
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
|
||||
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
|
||||
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
|
||||
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
|
||||
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
|
||||
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
|
||||
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
|
||||
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
|
||||
],
|
||||
"version": "==0.4.8"
|
||||
},
|
||||
|
|
@ -1159,7 +1129,6 @@
|
|||
"sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068",
|
||||
"sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.7.0"
|
||||
},
|
||||
"pygments": {
|
||||
|
|
@ -1167,7 +1136,6 @@
|
|||
"sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f",
|
||||
"sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==2.9.0"
|
||||
},
|
||||
"python-dateutil": {
|
||||
|
|
@ -1175,7 +1143,6 @@
|
|||
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
|
||||
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==2.8.1"
|
||||
},
|
||||
"pytz": {
|
||||
|
|
@ -1217,7 +1184,6 @@
|
|||
"sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
|
||||
"sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
|
||||
"version": "==5.4.1"
|
||||
},
|
||||
"requests": {
|
||||
|
|
@ -1233,7 +1199,7 @@
|
|||
"sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2",
|
||||
"sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"
|
||||
],
|
||||
"markers": "python_version >= '3.5' and python_version < '4'",
|
||||
"markers": "python_version > '2.7'",
|
||||
"version": "==4.7.2"
|
||||
},
|
||||
"s3transfer": {
|
||||
|
|
@ -1248,7 +1214,6 @@
|
|||
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
|
||||
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==1.16.0"
|
||||
},
|
||||
"sqlparse": {
|
||||
|
|
@ -1256,7 +1221,6 @@
|
|||
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
|
||||
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
|
||||
],
|
||||
"markers": "python_version >= '3.5'",
|
||||
"version": "==0.4.1"
|
||||
},
|
||||
"toml": {
|
||||
|
|
@ -1264,24 +1228,21 @@
|
|||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
||||
],
|
||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
||||
"version": "==0.10.2"
|
||||
},
|
||||
"traitlets": {
|
||||
"hashes": [
|
||||
"sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396",
|
||||
"sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"
|
||||
"sha256:70b4c6a1d9019d7b4f6846832288f86998aa3b9207c6821f3578a6a6a467fe44",
|
||||
"sha256:d023ee369ddd2763310e4c3eae1ff649689440d4ae59d7485eb4cfbbe3e359f7"
|
||||
],
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==5.0.5"
|
||||
"version": "==4.3.3"
|
||||
},
|
||||
"urllib3": {
|
||||
"hashes": [
|
||||
"sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
|
||||
"sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
|
||||
"sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c",
|
||||
"sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098"
|
||||
],
|
||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
|
||||
"version": "==1.26.4"
|
||||
"version": "==1.26.5"
|
||||
},
|
||||
"wcwidth": {
|
||||
"hashes": [
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
const schema = require('../../fixtures/schema_public.json');
|
||||
|
||||
describe('Email Verifcation', () => {
|
||||
beforeEach(() => {
|
||||
cy.server();
|
||||
});
|
||||
|
||||
it('forwards to homepage if confirmation key is correct', () => {
|
||||
cy.viewport('macbook-15');
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
operations: {
|
||||
Registration: {
|
||||
registration: {
|
||||
message: 'success',
|
||||
success: true
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||
|
||||
// user should be logged in at that stage. As the cookie cannot be set at the right time
|
||||
// we just check if the user gets redirected to the login page as we can't log her in
|
||||
cy.url().should('include', 'hello?redirect=%2F');
|
||||
});
|
||||
|
||||
it('displays error if key is incorrect', () => {
|
||||
cy.viewport('macbook-15');
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
// endpoint: '/api/graphql'
|
||||
operations: {
|
||||
Registration: {
|
||||
registration: {
|
||||
message: 'invalid_key',
|
||||
success: false
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||
cy.get('[data-cy="code-nok-msg"]').contains('Der angegebene Verifizierungscode ist ungültig oder abgelaufen.');
|
||||
cy.get('[data-cy="code-ok-msg"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('displays error if an error occured', () => {
|
||||
cy.viewport('macbook-15');
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
// endpoint: '/api/graphql'
|
||||
operations: {
|
||||
Registration: {
|
||||
registration: {
|
||||
message: 'unkown_error',
|
||||
success: false
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||
cy.get('[data-cy="code-nok-msg"]').contains('Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.');
|
||||
});
|
||||
|
||||
it('forwards to coupon page if user has no valid license', () => {
|
||||
cy.viewport('macbook-15');
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
// endpoint: '/api/graphql'
|
||||
operations: {
|
||||
Registration: {
|
||||
registration: {
|
||||
message: 'no_valid_license',
|
||||
success: false
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||
|
||||
// user should be logged in at that stage. As the cookie cannot be set at the right time
|
||||
// we just check if the user gets redirected to the coupon page as we can't log her in
|
||||
cy.url().should('include', 'hello?redirect=%2Flicense-activation');
|
||||
});
|
||||
});
|
||||
|
|
@ -1,164 +0,0 @@
|
|||
const isEmailAvailableUrl = 'https://stage.hep-verlag.ch/rest/deutsch/V1/customers/isEmailAvailable';
|
||||
const registerUrl = '/api/proxy/registration/';
|
||||
|
||||
let registrationResponse = {
|
||||
id: 84215,
|
||||
group_id: 1,
|
||||
confirmation: "91cf39007547feae7e33778d89fc71db",
|
||||
created_at: "2020-02-06 13:56:54",
|
||||
updated_at: "2020-02-06 13:56:54",
|
||||
created_in: "hep verlag",
|
||||
email: "feuz@aebi.ch",
|
||||
firstname: "Kari",
|
||||
lastname: "Feuz",
|
||||
prefix: "Herr",
|
||||
gender: 1,
|
||||
store_id: 1,
|
||||
website_id: 1,
|
||||
addresses: []
|
||||
};
|
||||
|
||||
describe('Registration', () => {
|
||||
beforeEach(() => {
|
||||
cy.viewport('macbook-15');
|
||||
cy.server();
|
||||
});
|
||||
|
||||
// it('works with valid data', () => {
|
||||
// cy.route('POST', isEmailAvailableUrl, "true");
|
||||
// cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
// cy.visit('/hello');
|
||||
// cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
// cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
// cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!', true);
|
||||
// cy.get('[data-cy="email-check"]').contains('Eine Email ist auf dem Weg, bitte überprüfen sie ihre E-mail Konto.');
|
||||
// });
|
||||
|
||||
it('displays error if firstname is missing', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, "true");
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, '', registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!', true);
|
||||
cy.get('[data-cy="firstname-local-errors"]').contains('Vorname ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('displays error if lastname is missing', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, "true");
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, '', 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!', true);
|
||||
cy.get('[data-cy="lastname-local-errors"]').contains('Nachname ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('displays error if street is missing', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, 'true');
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, '', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!', true);
|
||||
cy.get('[data-cy="street-local-errors"]').contains('Strasse ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('displays error if city is missing', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, 'true');
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', '', '3001', 'Abcd1234!', 'Abcd1234!', true);
|
||||
cy.get('[data-cy="city-local-errors"]').contains('Ort ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('displays error if postcode is missing', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, 'true');
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '', 'Abcd1234!', 'Abcd1234!', true);
|
||||
cy.get('[data-cy="postcode-local-errors"]').contains('Postleitzahl ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('displays error if password is missing', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, "true");
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', '', 'Abcd1234!', true);
|
||||
cy.get('[data-cy="password-local-errors"]').contains('Passwort ist ein Pflichtfeld');
|
||||
});
|
||||
|
||||
it('displays error if passwords are not secure', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, "true");
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234', 'Abcd1234', true);
|
||||
cy.get('[data-cy="password-local-errors"]').contains('Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein');
|
||||
});
|
||||
|
||||
it('displays error if passwords are too short', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, "true");
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd12!', 'Abcd12!', true);
|
||||
cy.get('[data-cy="password-local-errors"]').contains('Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein');
|
||||
});
|
||||
|
||||
it('displays error if passwords are not matching', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, "true");
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd129999!', true);
|
||||
cy.get('[data-cy="passwordConfirmation-local-errors"]').contains('Die Bestätigung von Passwort wiederholen stimmt nicht überein');
|
||||
});
|
||||
|
||||
it('displays error if terms are not accepted', () => {
|
||||
cy.route('POST', isEmailAvailableUrl, "true");
|
||||
cy.route('POST', registerUrl, registrationResponse);
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable(registrationResponse.email);
|
||||
|
||||
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!', false);
|
||||
cy.get('[data-cy="acceptedTerms-local-errors"]').contains('Sie müssen hier zustimmen, damit Sie sich registrieren können.');
|
||||
});
|
||||
|
||||
it('redirects to hello if email is missing', () => {
|
||||
cy.visit('/register');
|
||||
cy.get('[data-cy="hello-title"]').contains('Wollen Sie mySkillbox jetzt im Unterricht verwenden?');
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -48,7 +48,7 @@ describe('The Login Page', () => {
|
|||
|
||||
cy.get('[data-cy=logout]').click();
|
||||
|
||||
cy.get('[data-cy=email-input]').should('exist').within(() => {
|
||||
cy.get('[data-cy=oauth-login]').should('exist').within(() => {
|
||||
cy.visit('/beta-login');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,73 +0,0 @@
|
|||
const schema = require('../../../fixtures/schema_public.json');
|
||||
const isEmailAvailableUrl = '**/rest/deutsch/V1/customers/isEmailAvailable';
|
||||
const checkPasswordUrl = '**/rest/deutsch/V1/integration/customer/token';
|
||||
|
||||
describe('Login', () => {
|
||||
beforeEach(() => {
|
||||
cy.server();
|
||||
});
|
||||
|
||||
it('works with valid email and password', () => {
|
||||
cy.viewport('macbook-15');
|
||||
|
||||
cy.mockGraphql({
|
||||
schema: schema,
|
||||
operations: {
|
||||
Login: variables => {
|
||||
return {
|
||||
login: {
|
||||
errors: [],
|
||||
message: 'success',
|
||||
success: true
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
cy.route('POST', isEmailAvailableUrl, 'false');
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
url: checkPasswordUrl,
|
||||
response: 'token12345ABCD+',
|
||||
});
|
||||
|
||||
cy.visit('/hello');
|
||||
cy.checkEmailAvailable('feuz@aebi.ch');
|
||||
|
||||
cy.get('[data-cy="login-title"]').contains('Bitte geben Sie das passende Passwort ein');
|
||||
cy.enterPassword('abcd1234');
|
||||
// As we cannot set the cookie in the right manner, we just check for the absence of errors.
|
||||
// In real world the user gets redirect to another page
|
||||
cy.get('[data-cy="email-local-errors"]').should('not.exist');
|
||||
});
|
||||
|
||||
it('displays error message if password is wrong', () => {
|
||||
cy.viewport('macbook-15');
|
||||
cy.route('POST', isEmailAvailableUrl, 'false');
|
||||
cy.route({
|
||||
method: 'POST',
|
||||
status: 401,
|
||||
response: {
|
||||
message: 'Sie haben sich nicht korrekt eingeloggt oder Ihr Konto ist vor\u00fcbergehend deaktiviert.'
|
||||
},
|
||||
url: checkPasswordUrl
|
||||
});
|
||||
|
||||
cy.visit('/hello');
|
||||
|
||||
cy.checkEmailAvailable('feuz@aebi.ch');
|
||||
cy.get('[data-cy="login-title"]').contains('Bitte geben Sie das passende Passwort ein');
|
||||
|
||||
cy.enterPassword('abcd1234');
|
||||
cy.get('[data-cy="password-errors"]').contains('Die von Ihnen eingegebene E-Mail-Adresse und das Passwort passen nicht zusammen.');
|
||||
});
|
||||
|
||||
it('displays error message if input is not an email address', () => {
|
||||
cy.viewport('macbook-15');
|
||||
cy.visit('/hello');
|
||||
|
||||
cy.checkEmailAvailable('feuzaebi.ch');
|
||||
cy.get('[data-cy="email-local-errors"]').contains('Bitte geben Sie eine gülitge E-Mail an');
|
||||
});
|
||||
});
|
||||
|
|
@ -53,7 +53,7 @@ describe('Custom Content Block', () => {
|
|||
cy.viewport('macbook-15');
|
||||
});
|
||||
|
||||
it('Deletes the custom content block and removes it from the view', () => {
|
||||
it.skip('Deletes the custom content block and removes it from the view', () => {
|
||||
cy.fakeLogin('nico.zickgraf', 'test');
|
||||
cy.visit('module/some-module');
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -36,7 +36,6 @@
|
|||
"apollo-link-http": "^1.5.16",
|
||||
"appolo": "^6.0.19",
|
||||
"autoprefixer": "^7.1.2",
|
||||
"axios": "^0.18.0",
|
||||
"babel-eslint": "^8.2.1",
|
||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||
"babel-loader": "^8.0.6",
|
||||
|
|
@ -87,7 +86,6 @@
|
|||
"vue": "^2.5.17",
|
||||
"vue-analytics": "^5.16.2",
|
||||
"vue-apollo": "^3.0.0-beta.16",
|
||||
"vue-axios": "^2.1.1",
|
||||
"vue-loader": "^15.9.6",
|
||||
"vue-matomo": "^3.13.4-0",
|
||||
"vue-router": "^3.0.1",
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import FullScreenLayout from '@/layouts/FullScreenLayout';
|
||||
import PublicLayout from '@/layouts/PublicLayout';
|
||||
import BlankLayout from '@/layouts/BlankLayout';
|
||||
import SplitLayout from '@/layouts/SplitLayout';
|
||||
import Modal from '@/components/Modal';
|
||||
import NewContentBlockWizard from '@/components/content-block-form/NewContentBlockWizard';
|
||||
import EditContentBlockWizard from '@/components/content-block-form/EditContentBlockWizard';
|
||||
|
|
@ -50,6 +51,7 @@
|
|||
FullScreenLayout,
|
||||
PublicLayout,
|
||||
BlankLayout,
|
||||
SplitLayout,
|
||||
Modal,
|
||||
NewContentBlockWizard,
|
||||
EditContentBlockWizard,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<svg
|
||||
viewBox="0 0 65.3274 22.28203"
|
||||
data-name="Ebene 1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="Ebene_1">
|
||||
<title>Zeichenfläche 1</title>
|
||||
<circle
|
||||
class="cls-1"
|
||||
cx="11.14101"
|
||||
cy="11.14101"
|
||||
r="11.14101"/>
|
||||
<path
|
||||
class="cls-1"
|
||||
d="M38.24333,9.54537a4.55137,4.55137,0,0,0-.89344-1.506,3.95851,3.95851,0,0,0-1.37354-.97187,4.42839,4.42839,0,0,0-1.76251-.34656,4.00351,4.00351,0,0,0-1.80913.40273,3.454,3.454,0,0,0-1.30783,1.14356h-.03922V3.51107H28.425V18.74049h2.63262V11.78056a3.04623,3.04623,0,0,1,.17381-1.03228,2.49273,2.49273,0,0,1,.49813-.8468,2.33973,2.33973,0,0,1,.79169-.56277,2.52554,2.52554,0,0,1,1.03016-.20561,2.30456,2.30456,0,0,1,1.72223.68041,2.663,2.663,0,0,1,.66027,1.9236v7.00338h2.63263V11.5156A5.72892,5.72892,0,0,0,38.24333,9.54537Z"/>
|
||||
<path
|
||||
class="cls-1"
|
||||
d="M51.53949,10.29785a5.35711,5.35711,0,0,0-1.081-1.8494,5.222,5.222,0,0,0-1.74873-1.25909,5.72952,5.72952,0,0,0-2.37085-.46844,6.03146,6.03146,0,0,0-2.2691.41333,4.92935,4.92935,0,0,0-1.7625,1.19125,5.3972,5.3972,0,0,0-1.13508,1.91618,7.81512,7.81512,0,0,0-.40062,2.59977,7.81313,7.81313,0,0,0,.40062,2.59872,5.37539,5.37539,0,0,0,1.13508,1.91617,4.91636,4.91636,0,0,0,1.7837,1.19338,6.37091,6.37091,0,0,0,2.33587.41121,6.17525,6.17525,0,0,0,1.95857-.29039,5.031,5.031,0,0,0,1.51556-.7917,4.95477,4.95477,0,0,0,1.09163-1.17,5.74653,5.74653,0,0,0,.69-1.42442l.02014-.06041H48.856l-.01378.01908a4.05035,4.05035,0,0,1-.95067.939,2.503,2.503,0,0,1-1.44243.3667,2.73106,2.73106,0,0,1-2.07516-.81289,3.29407,3.29407,0,0,1-.85634-2.07515h8.272l.00636-.03922c.01377-.089.02861-.19183.04345-.31053.01484-.1028.02967-.2215.04451-.354a4.07907,4.07907,0,0,0,.02332-.44725A6.72183,6.72183,0,0,0,51.53949,10.29785ZM46.33888,9.1331a2.61116,2.61116,0,0,1,1.94374.71539,3.20648,3.20648,0,0,1,.87224,1.7307H43.525A3.3678,3.3678,0,0,1,44.47146,9.805,2.62773,2.62773,0,0,1,46.33888,9.1331Z"/>
|
||||
<path
|
||||
class="cls-1"
|
||||
d="M64.948,10.20883a5.456,5.456,0,0,0-1.05665-1.91512A4.53254,4.53254,0,0,0,62.273,7.12259a5.23843,5.23843,0,0,0-2.072-.40167,4.12271,4.12271,0,0,0-2.06667.50342,4.59491,4.59491,0,0,0-1.37885,1.1531H56.7046l-.32431-1.4-.00742-.036H54.19068V22.282h2.63368v-4.8674h.03816a2.65756,2.65756,0,0,0,.4589.5066,3.83173,3.83173,0,0,0,.74825.514,4.59629,4.59629,0,0,0,.9708.38048,4.445,4.445,0,0,0,1.16052.1452,5.23825,5.23825,0,0,0,2.072-.40168,4.5324,4.5324,0,0,0,1.61837-1.17111A5.43931,5.43931,0,0,0,64.948,15.473a8.40143,8.40143,0,0,0,.37942-2.63157A8.41306,8.41306,0,0,0,64.948,10.20883ZM59.759,9.1331a2.87335,2.87335,0,0,1,1.14144.22786,2.62117,2.62117,0,0,1,.93265.68359,3.25518,3.25518,0,0,1,.63166,1.15522,5.28409,5.28409,0,0,1,.22893,1.64168,5.29354,5.29354,0,0,1-.22893,1.64169,3.25917,3.25917,0,0,1-.63166,1.15415,2.59365,2.59365,0,0,1-.93265.6836,2.97289,2.97289,0,0,1-2.28288,0,2.59365,2.59365,0,0,1-.93265-.6836,3.277,3.277,0,0,1-.63166-1.15521,5.28178,5.28178,0,0,1-.22893-1.64063,5.28409,5.28409,0,0,1,.22893-1.64168,3.27711,3.27711,0,0,1,.63166-1.15522A2.62125,2.62125,0,0,1,58.6176,9.361,2.87335,2.87335,0,0,1,59.759,9.1331Z"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.cls-1 {
|
||||
fill: #002f6c;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,6 +0,0 @@
|
|||
mutation Registration($input: RegistrationInput!) {
|
||||
registration(input: $input) {
|
||||
success
|
||||
message
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import * as axios from 'axios';
|
||||
|
||||
const hepBaseUrl = process.env.HEP_URL;
|
||||
|
||||
export function register(registrationData) {
|
||||
return axios.post('/api/proxy/registration/', registrationData);
|
||||
}
|
||||
|
||||
export function login(username, password) {
|
||||
return axios.post(`${hepBaseUrl}/rest/deutsch/V1/integration/customer/token`, {username, password});
|
||||
}
|
||||
|
||||
export function emailExists(email) {
|
||||
return axios.post(`${hepBaseUrl}/rest/deutsch/V1/customers/isEmailAvailable`, {
|
||||
customerEmail: email,
|
||||
websiteId: 1
|
||||
});
|
||||
}
|
||||
|
|
@ -53,6 +53,7 @@
|
|||
&__content {
|
||||
@include content-block();
|
||||
margin-bottom: $large-spacing;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&__logo {
|
||||
|
|
@ -67,7 +68,7 @@
|
|||
}
|
||||
|
||||
.footer {
|
||||
padding-top: $large-spacing;
|
||||
padding: $large-spacing $medium-spacing 0;
|
||||
|
||||
&__content {
|
||||
@include content-block();
|
||||
|
|
|
|||
|
|
@ -12,14 +12,18 @@
|
|||
.simple-footer {
|
||||
width: 100%;
|
||||
justify-self: center;
|
||||
padding: $large-spacing 0;
|
||||
padding: $large-spacing $small-spacing;
|
||||
background-color: $color-silver-light;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr $footer-width 1fr;
|
||||
grid-template-columns: 0 1fr 0;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
height: 105px;
|
||||
|
||||
@include desktop {
|
||||
grid-template-columns: 1fr $footer-width 1fr;
|
||||
}
|
||||
|
||||
&__strong {
|
||||
@include aside-with-cheese;
|
||||
font-weight: 600;
|
||||
|
|
|
|||
|
|
@ -71,7 +71,9 @@
|
|||
}
|
||||
|
||||
&__footer {
|
||||
grid-column: 1 / span 3;
|
||||
@include desktop {
|
||||
grid-column: 1 / span 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,154 @@
|
|||
<template>
|
||||
<div :class="['split-view', {'split-view--illustration': illustration}]">
|
||||
<div :class="['split-view__illustration', illustrationAlignment]">
|
||||
<component :is="illustration"/>
|
||||
</div>
|
||||
<div class="split-view__content">
|
||||
<router-view/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContentsIllustration from '@/components/illustrations/ContentsIllustration';
|
||||
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
|
||||
import RoomsIllustration from '@/components/illustrations/RoomsIllustration';
|
||||
import HelloIllustration from '@/components/illustrations/HelloIllustration';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
contents: ContentsIllustration,
|
||||
portfolio: PortfolioIllustration,
|
||||
rooms: RoomsIllustration,
|
||||
hello: HelloIllustration
|
||||
},
|
||||
|
||||
computed: {
|
||||
illustration() {
|
||||
return this.$route.meta.illustration;
|
||||
},
|
||||
illustrationAlignment() {
|
||||
return this.$route.meta.illustrationAlign ? `split-view__illustration--${this.$route.meta.illustrationAlign}` : '';
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.split-view {
|
||||
background-color: $color-brand;
|
||||
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
&--illustration {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
top: 50%;
|
||||
background: $color-brand-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&__illustration {
|
||||
width: 30vw;
|
||||
min-width: 400px;
|
||||
align-self: center;
|
||||
background-color: $color-brand;
|
||||
z-index: 1;
|
||||
display: none;
|
||||
|
||||
&--top {
|
||||
align-self: start;
|
||||
margin-top: $large-spacing;
|
||||
}
|
||||
|
||||
@include desktop {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
background-color: $color-white;
|
||||
padding: $medium-spacing;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
|
||||
@include desktop {
|
||||
padding: 2*$large-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&__logo {
|
||||
width: 300px;
|
||||
height: 50px;
|
||||
margin-bottom: $large-spacing;
|
||||
|
||||
@include desktop {
|
||||
margin-bottom: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
&__page-subheading {
|
||||
@include regular-text;
|
||||
color: $color-brand;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
|
||||
&__page-heading {
|
||||
@include heading-2;
|
||||
color: $color-brand;
|
||||
margin-bottom: 2*$large-spacing;
|
||||
}
|
||||
|
||||
&__heading {
|
||||
@include heading-2;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
|
||||
&__claim {
|
||||
@include heading-2;
|
||||
margin-bottom: 70px;
|
||||
}
|
||||
|
||||
&__paragraph {
|
||||
@include regular-text;
|
||||
margin-bottom: $medium-spacing;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 2*$large-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
@include regular-text;
|
||||
flex-grow: 0;
|
||||
align-self: flex-start;
|
||||
min-width: 150px;
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
margin-bottom: $large-spacing;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&__secondary-link {
|
||||
@include inline-title;
|
||||
cursor: pointer;
|
||||
|
||||
@include desktop {
|
||||
margin-top: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
import '@babel/polyfill';
|
||||
import Vue from 'vue';
|
||||
import axios from 'axios';
|
||||
import VueAxios from 'vue-axios';
|
||||
import VueVimeoPlayer from 'vue-vimeo-player';
|
||||
import apolloClientFactory from './graphql/client';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import App from './App';
|
||||
import router from './router';
|
||||
import {router, postLoginRedirectUrlKey} from './router';
|
||||
import store from '@/store/index';
|
||||
import VueScrollTo from 'vue-scrollto';
|
||||
import {Validator, install as VeeValidate} from 'vee-validate/dist/vee-validate.minimal.esm.js';
|
||||
|
|
@ -28,7 +26,6 @@ const isProduction = process.env.NODE_ENV === 'production';
|
|||
Vue.use(VueModal);
|
||||
Vue.use(VueRemoveEdges);
|
||||
Vue.use(VueApollo);
|
||||
Vue.use(VueAxios, axios);
|
||||
Vue.use(VueVimeoPlayer);
|
||||
Vue.use(VueToast);
|
||||
Vue.use(VueLogger, {
|
||||
|
|
@ -161,7 +158,13 @@ router.beforeEach(async (to, from, next) => {
|
|||
}
|
||||
|
||||
if (unauthorizedAccess(to)) {
|
||||
const redirectUrl = `/hello?redirect=${to.path}`;
|
||||
const postLoginRedirectionUrl = to.path;
|
||||
const redirectUrl = `/hello/`;
|
||||
|
||||
if (window.localStorage) {
|
||||
localStorage.setItem(postLoginRedirectUrlKey, postLoginRedirectionUrl);
|
||||
}
|
||||
|
||||
next(redirectUrl);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,13 +72,6 @@
|
|||
class="button button--primary button--big actions__submit"
|
||||
data-cy="login-button">Anmelden</button>
|
||||
</div>
|
||||
<div class="account-link">
|
||||
<p class="account-link__text">Haben Sie noch kein Konto?</p>
|
||||
<router-link
|
||||
:to="{name: 'registration'}"
|
||||
class="account-link__link text-link">Jetzt registrieren
|
||||
</router-link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
<template>
|
||||
<div class="check-email">
|
||||
<main
|
||||
class="check-email__content content"
|
||||
data-cy="email-check">
|
||||
<p class="content__instructions">Eine E-Mail ist auf dem Weg, bitte überprüfen Sie Ihr Postfach.</p>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
|
||||
.content__instructions {
|
||||
margin-top: $medium-spacing;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
<template>
|
||||
<div class="emailconfirmation public-page">
|
||||
<h1 class="emailconfirmation__title public-page__title">Überprüfung der E-Mail Adresse</h1>
|
||||
<p v-if="loading">Der Verifikationscode wird überprüft.</p>
|
||||
<p
|
||||
data-cy="code-ok-msg"
|
||||
v-if="showOkMessage">Der Verifikationscode ist gültig. Sie werden weitergeleitet.</p>
|
||||
<p
|
||||
data-cy="code-nok-msg"
|
||||
v-if="showErrorMessage">{{ errorMessage }}</p>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import REGISTRATION_MUTATION from '@/graphql/gql/mutations/registration.gql';
|
||||
|
||||
export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
keyValid: false,
|
||||
errorMessage: ''
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
showOkMessage() {
|
||||
return !this.loading && this.keyValid;
|
||||
},
|
||||
showErrorMessage() {
|
||||
return !this.loading && !this.keyValid;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.$apollo.mutate({
|
||||
mutation: REGISTRATION_MUTATION,
|
||||
client: 'publicClient',
|
||||
variables: {
|
||||
input: {
|
||||
confirmationKey: this.$route.query.confirmation,
|
||||
userId: this.$route.query.id
|
||||
}
|
||||
},
|
||||
fetchPolicy: 'no-cache'
|
||||
}).then(({data}) => {
|
||||
this.loading = false;
|
||||
if (data.registration.success) {
|
||||
this.keyValid = true;
|
||||
|
||||
if (data.registration.message === 'no_valid_license') {
|
||||
this.$router.push({name: 'licenseActivation'});
|
||||
} else {
|
||||
this.$router.push('/');
|
||||
}
|
||||
} else {
|
||||
switch (data.registration.message) {
|
||||
case 'invalid_key':
|
||||
this.errorMessage = 'Der angegebene Verifizierungscode ist ungültig oder abgelaufen.';
|
||||
break;
|
||||
case 'no_valid_license':
|
||||
this.$router.push({name: 'licenseActivation'});
|
||||
break;
|
||||
default:
|
||||
this.errorMessage = 'Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.errorMessage = 'Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.';
|
||||
});
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.text-link {
|
||||
font-family: $sans-serif-font-family;
|
||||
color: $color-brand;
|
||||
}
|
||||
|
||||
.actions {
|
||||
&__reset {
|
||||
display: inline-block;
|
||||
margin-left: $large-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<template>
|
||||
<div class="forgot-password public-page">
|
||||
<header class="info-header">
|
||||
<h1
|
||||
class="forgot-password__title public-page__title"
|
||||
data-cy="forgot-password">Passwort vergessen?</h1>
|
||||
</header>
|
||||
<section class="forgot-password__section forgot-password__text">
|
||||
<p class="forgot-info">
|
||||
Ihr Benutzerkonto wird durch den hep Verlag verwaltet. Um Ihr Passwort zurückzusetzen,
|
||||
gehen Sie
|
||||
auf die Verlagsseite.</p>
|
||||
</section>
|
||||
<section class="forgot-password__section forgot-password__text">
|
||||
<p class="forgot-info">
|
||||
Passwort unter
|
||||
<a
|
||||
class="hep-link"
|
||||
href="https://www.hep-verlag.ch/customer/account/forgotpassword/"
|
||||
target="_blank">www.hep-verlag.ch</a> zurücksetzen</p>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.forgot-info {
|
||||
font-family: $sans-serif-font-family;
|
||||
margin-bottom: $medium-spacing;
|
||||
|
||||
}
|
||||
|
||||
.forgot-password__link {
|
||||
margin-top: $large-spacing;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,59 +1,81 @@
|
|||
<template>
|
||||
<div class="hello public-page">
|
||||
<h1
|
||||
class="hello__title public-page__title"
|
||||
data-cy="hello-title">Wollen Sie {{ pageTitle }} jetzt im Unterricht verwenden?</h1>
|
||||
<form
|
||||
class="hello__form hello-form"
|
||||
novalidate
|
||||
@submit.prevent="validateBeforeSubmit">
|
||||
<div class="hello-form__field skillboxform-input">
|
||||
<label
|
||||
for="email"
|
||||
class="skillboxform-input__label">E-Mail</label>
|
||||
<input
|
||||
v-model="email"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
||||
name="email"
|
||||
type="email"
|
||||
data-vv-as="E-Mail"
|
||||
class="change-form__email skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="email-input"
|
||||
placeholder="E-Mail eingeben"
|
||||
tabindex="0"
|
||||
id="email"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="email-local-errors"
|
||||
v-if="errors.has('email') && submitted"
|
||||
>{{ errors.first('email') }}</small>
|
||||
<div
|
||||
class="hello"
|
||||
data-cy="hello-page">
|
||||
<div class="about">
|
||||
<div class="about__logos logos">
|
||||
<a
|
||||
href="https://www.hep-verlag.ch/"
|
||||
target="_blank">
|
||||
<hep-logo-no-claim class="logos__logo" />
|
||||
</a>
|
||||
<a
|
||||
href="https://www.ehb.swiss/"
|
||||
target="_blank">
|
||||
<ehb-logo class="logos__logo" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<loading-button
|
||||
:loading="loading"
|
||||
label="Los geht's"
|
||||
class="actions__submit"
|
||||
data-cy="hello-button"
|
||||
/>
|
||||
<p class="about__text">mySkillbox ist ein Angebot des hep Verlags in
|
||||
Zusammenarbeit mit dem Eidgenössischen Hochschulinstitut für Berufsbildung.</p>
|
||||
</div>
|
||||
<logo class="logo" />
|
||||
<div class="login-actions">
|
||||
<h2
|
||||
class="login-actions__title"
|
||||
data-cy="hello-title">Wollen Sie {{ pageTitle }} im Unterricht verwenden?</h2>
|
||||
<a
|
||||
class="button button--primary button--big actions__submit"
|
||||
href="/api/oauth/login/"
|
||||
data-cy="oauth-login">Mit hep Konto anmelden</a>
|
||||
|
||||
<div class="login-actions__register register">
|
||||
<p>Haben Sie noch kein hep Konto?</p>
|
||||
<a
|
||||
class="hep-link"
|
||||
href="/api/oauth/login/"
|
||||
data-cy="oauth-login">Jetzt registrieren</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="information">
|
||||
<p>Was ist ein hep Konto und wie kann ich mich dafür registrieren?</p>
|
||||
<a
|
||||
class="hep-link"
|
||||
href="https://myskillbox.ch/anleitung"
|
||||
data-cy="oauth-login">Anleitung anschauen</a>
|
||||
</div>
|
||||
<div class="links">
|
||||
<ul class="links__list">
|
||||
<li class="links__list-item">
|
||||
<a href="">Lizenz kaufen</a>
|
||||
</li>
|
||||
<li class="links__list-item">
|
||||
<a href="">Support</a>
|
||||
</li>
|
||||
<li class="links__list-item">
|
||||
<a href="">Datenschutz</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {emailExists} from '../hep-client/index';
|
||||
import HELLO_EMAIL_MUTATION from '@/graphql/gql/local/mutations/helloEmail.gql';
|
||||
import LoadingButton from '@/components/LoadingButton';
|
||||
import pageTitleMixin from '@/mixins/page-title';
|
||||
import HepLogoNoClaim from '@/components/icons/HepLogoNoClaim';
|
||||
import EhbLogo from '@/components/icons/EhbLogo';
|
||||
import Logo from '@/components/icons/Logo';
|
||||
import HelloIllustration from '@/components/illustrations/HelloIllustration';
|
||||
|
||||
export default {
|
||||
mixins: [pageTitleMixin],
|
||||
components: {
|
||||
LoadingButton
|
||||
LoadingButton,
|
||||
HelloIllustration,
|
||||
HepLogoNoClaim,
|
||||
EhbLogo,
|
||||
Logo
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -64,41 +86,6 @@
|
|||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
validateBeforeSubmit() {
|
||||
this.$validator.validate().then(result => {
|
||||
this.submitted = true;
|
||||
if (result) {
|
||||
this.loading = true;
|
||||
emailExists(this.email).then((response) => {
|
||||
let redirectRouteName = 'login';
|
||||
|
||||
if (response.data) {
|
||||
redirectRouteName = 'registration';
|
||||
}
|
||||
this.$apollo.mutate({
|
||||
mutation: HELLO_EMAIL_MUTATION,
|
||||
variables: {
|
||||
helloEmail: this.email
|
||||
}
|
||||
}).then(() => {
|
||||
this.$router.push({name: redirectRouteName, query: this.$route.query});
|
||||
this.loading = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
this.registrationError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals.';
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
resetForm() {
|
||||
this.email = '';
|
||||
this.submitted = false;
|
||||
this.$validator.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -106,15 +93,106 @@
|
|||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.text-link {
|
||||
font-family: $sans-serif-font-family;
|
||||
color: $color-brand;
|
||||
$hello-block-margin: 2*$medium-spacing;
|
||||
|
||||
.hello {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
@include desktop {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
&__reset {
|
||||
display: inline-block;
|
||||
margin-left: $large-spacing;
|
||||
.logo {
|
||||
display: block;
|
||||
width: 300px;
|
||||
margin: $small-spacing auto $hello-block-margin;
|
||||
|
||||
@include desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.about {
|
||||
display: none;
|
||||
margin-bottom: $hello-block-margin;
|
||||
|
||||
@include desktop {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__text {
|
||||
margin-top: $medium-spacing;
|
||||
@include regular-text;
|
||||
}
|
||||
|
||||
&__logos {
|
||||
& a:first-child {
|
||||
margin-right: $large-spacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logos {
|
||||
&__logo {
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-actions {
|
||||
@include widget-shadow;
|
||||
padding: $medium-spacing;
|
||||
margin-bottom: $hello-block-margin;
|
||||
|
||||
&__title {
|
||||
font-size: 2.125rem; // 34px
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&__register {
|
||||
margin-top: $large-spacing;
|
||||
|
||||
> p, a {
|
||||
@include regular-text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.information {
|
||||
margin-top: $hello-block-margin;
|
||||
> p, a {
|
||||
@include regular-text;
|
||||
}
|
||||
}
|
||||
|
||||
.links {
|
||||
margin-top: $hello-block-margin;
|
||||
display: flex;
|
||||
|
||||
&__list-item {
|
||||
|
||||
color: $color-silver-dark;
|
||||
|
||||
> a {
|
||||
@include regular-text;
|
||||
}
|
||||
|
||||
flex-direction: column;
|
||||
margin-top: $medium-spacing;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@include desktop {
|
||||
display: inline-block;
|
||||
flex-direction: row;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<div class="login-error">
|
||||
<header class="info-header">
|
||||
<h1
|
||||
class="public-page__title"
|
||||
data-cy="login-error-title">{{ title }}</h1>
|
||||
</header>
|
||||
<main
|
||||
class="login-error__content content"
|
||||
data-cy="login-error-text">
|
||||
<p class="content__instructions">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<p>
|
||||
<router-link
|
||||
:to="{name: 'hello'}"
|
||||
class="hep-link">
|
||||
Startseite anzeigen
|
||||
</router-link>
|
||||
</p>
|
||||
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LoginErrorPage',
|
||||
props: ['title', 'errorMessage']
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.content__instructions {
|
||||
margin: $medium-spacing 0;
|
||||
@include regular-text;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
<template>
|
||||
<div class="login public-page">
|
||||
<header class="info-header">
|
||||
<p class="info-header__text small-emph">Super, wir haben für <span
|
||||
class="info-header__emph">{{ helloEmail.email }}</span> ein hep-Konto gefunden.</p>
|
||||
<h1
|
||||
class="login__title public-page__title"
|
||||
data-cy="login-title">Bitte geben Sie das passende Passwort ein</h1>
|
||||
</header>
|
||||
<form
|
||||
class="login__form login-form"
|
||||
novalidate
|
||||
@submit.prevent="validateBeforeSubmit">
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label
|
||||
for="password"
|
||||
class="skillboxform-input__label">Passwort</label>
|
||||
<input
|
||||
v-model="password"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('password') }"
|
||||
name="password"
|
||||
type="password"
|
||||
data-vv-as="Passwort"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="password-input"
|
||||
tabindex="0"
|
||||
id="password"
|
||||
>
|
||||
<small
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="password-errors"
|
||||
v-for="error in passwordErrors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<loading-button
|
||||
:loading="loading"
|
||||
class="actions__submit"
|
||||
data-cy="login-button"
|
||||
label="Anmelden"
|
||||
/>
|
||||
<router-link
|
||||
:to="{name: 'hello'}"
|
||||
tag="button"
|
||||
class="button button--big actions__submit back-button">Abbrechen
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{name: 'forgotPassword'}"
|
||||
class="actions__reset text-link">Passwort vergessen?</router-link>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
||||
import LOGIN_MUTATION from '@/graphql/gql/mutations/login.gql';
|
||||
import {login} from '@/hep-client/index';
|
||||
import LoadingButton from '@/components/LoadingButton';
|
||||
|
||||
export default {
|
||||
components: {LoadingButton},
|
||||
|
||||
data() {
|
||||
return {
|
||||
password: '',
|
||||
passwordErrors: [],
|
||||
submitted: false,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
validateBeforeSubmit() {
|
||||
this.$validator.validate().then(result => {
|
||||
this.submitted = true;
|
||||
if (result) {
|
||||
this.loading = true;
|
||||
login(this.helloEmail.email, this.password)
|
||||
.then((response) => {
|
||||
this.loading = false;
|
||||
if (response.status === 200) {
|
||||
this.mySkillboxLogin(response.data);
|
||||
} else {
|
||||
this.passwordErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.'];
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
if (error.response.data.message && error.response.data.message === 'Sie haben sich nicht korrekt eingeloggt oder Ihr Konto ist vor\u00fcbergehend deaktiviert.') {
|
||||
this.passwordErrors = ['Die von Ihnen eingegebene E-Mail-Adresse und das Passwort passen nicht zusammen.'];
|
||||
} else {
|
||||
this.passwordErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.'];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
mySkillboxLogin(token) {
|
||||
const that = this;
|
||||
this.$apollo.mutate({
|
||||
client: 'publicClient',
|
||||
mutation: LOGIN_MUTATION,
|
||||
variables: {
|
||||
input: {
|
||||
tokenInput: token
|
||||
}
|
||||
},
|
||||
update(
|
||||
store,
|
||||
{
|
||||
data: {
|
||||
login
|
||||
}
|
||||
}
|
||||
) {
|
||||
try {
|
||||
if (login.success) {
|
||||
if (login.message === 'no_valid_license') {
|
||||
that.$router.push({name: 'licenseActivation'});
|
||||
} else {
|
||||
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/';
|
||||
that.$router.push(redirectUrl);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
that.passwordErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.'];
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(errors => {
|
||||
const firstError = errors.graphQLErrors[0];
|
||||
switch (firstError.message) {
|
||||
case 'invalid_credentials':
|
||||
that.passwordErrors = ['Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.'];
|
||||
break;
|
||||
case 'email_not_verified':
|
||||
that.passwordErrors = ['Bitte verifiziere zuerst deine E-Mail.'];
|
||||
break;
|
||||
case 'no_valid_license':
|
||||
this.$router.push({name: 'licenseActivation'});
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
resetForm() {
|
||||
this.password = '';
|
||||
this.submitted = false;
|
||||
this.$validator.reset();
|
||||
},
|
||||
},
|
||||
|
||||
apollo: {
|
||||
helloEmail: {
|
||||
query: HELLO_EMAIL,
|
||||
result({data: {helloEmail}}) {
|
||||
if (helloEmail.email === '') {
|
||||
this.$router.push({name: 'hello'});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.text-link {
|
||||
font-family: $sans-serif-font-family;
|
||||
color: $color-brand;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
|
||||
&__reset {
|
||||
display: inline-block;
|
||||
margin-left: auto;
|
||||
line-height: 19px;;
|
||||
padding: $small-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
.back-button {
|
||||
//font-size: 1rem;
|
||||
line-height: normal;
|
||||
margin-left: $medium-spacing;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,40 +1,26 @@
|
|||
<template>
|
||||
<div
|
||||
:class="['onboarding', {'onboarding--illustration': illustration}]"
|
||||
class="onboarding__content"
|
||||
data-cy="onboarding-page">
|
||||
<div class="onboarding__illustration">
|
||||
<component :is="illustration"/>
|
||||
</div>
|
||||
<div class="onboarding__content">
|
||||
<router-view/>
|
||||
<a
|
||||
class="onboarding__button button button--primary button--big"
|
||||
data-cy="onboarding-next-link"
|
||||
@click="next">{{ nextLabel }}
|
||||
</a>
|
||||
<a
|
||||
class="onboarding__secondary-link"
|
||||
data-cy="onboarding-skip-link"
|
||||
@click.prevent="completeOnboarding">Einführung überspringen</a>
|
||||
</div>
|
||||
<router-view/>
|
||||
<a
|
||||
class="onboarding__button button button--primary button--big"
|
||||
data-cy="onboarding-next-link"
|
||||
@click="next">{{ nextLabel }}
|
||||
</a>
|
||||
<a
|
||||
class="onboarding__secondary-link"
|
||||
data-cy="onboarding-skip-link"
|
||||
@click.prevent="completeOnboarding">Einführung überspringen</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContentsIllustration from '@/components/illustrations/ContentsIllustration';
|
||||
import PortfolioIllustration from '@/components/illustrations/PortfolioIllustration';
|
||||
import RoomsIllustration from '@/components/illustrations/RoomsIllustration';
|
||||
|
||||
import UPDATE_ONBOARDING_PROGRESS from '@/graphql/gql/mutations/updateOnboardingProgress.gql';
|
||||
import ME_QUERY from '@/graphql/gql/queries/meQuery';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
contents: ContentsIllustration,
|
||||
portfolio: PortfolioIllustration,
|
||||
rooms: RoomsIllustration
|
||||
},
|
||||
|
||||
computed: {
|
||||
nextLabel() {
|
||||
return this.$route.name === 'onboarding-start' ? 'Einführung starten' : 'Weiter';
|
||||
|
|
|
|||
|
|
@ -1,423 +0,0 @@
|
|||
<template>
|
||||
<div class="registration public-page">
|
||||
<header class="info-header">
|
||||
<p class="info-header__text small-emph">Für <span class="info-header__emph">{{ helloEmail }}</span> haben wir kein
|
||||
Hep Konto gefunden.</p>
|
||||
<h1
|
||||
class="registration__title public-page__title"
|
||||
data-cy="registration-title">Damit Sie {{ pageTitle }} verwenden können, müssen Sie ein Konto erstellen.</h1>
|
||||
</header>
|
||||
<form
|
||||
class="registration__form registration-form"
|
||||
novalidate
|
||||
@submit.prevent="validateBeforeSubmit">
|
||||
<div class="registration-form__field skillboxform-input">
|
||||
<div class="registration-form__field skillboxform-input">
|
||||
<label
|
||||
for="prefix"
|
||||
class="skillboxform-input__label">Anrede</label>
|
||||
<select
|
||||
v-model="prefix"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('prefix') }"
|
||||
name="prefix"
|
||||
data-vv-as="Prefix"
|
||||
class="change-form__prefix skillbox-input skillboxform-input__input skillbox-dropdown"
|
||||
autocomplete="off"
|
||||
data-cy="prefix-selection"
|
||||
id="prefix"
|
||||
>
|
||||
<option
|
||||
value="Herr"
|
||||
selected>Herr
|
||||
</option>
|
||||
<option value="Frau">Frau</option>
|
||||
</select>
|
||||
</div>
|
||||
<label
|
||||
for="firstname"
|
||||
class="skillboxform-input__label">Vorname</label>
|
||||
<input
|
||||
v-model="firstname"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('firstname') }"
|
||||
name="firstname"
|
||||
type="text"
|
||||
data-vv-as="Vorname"
|
||||
class="change-form__firstname skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="firstname-input"
|
||||
id="firstname"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="firstname-local-errors"
|
||||
v-if="errors.has('firstname') && submitted"
|
||||
>{{ errors.first('firstname') }}</small>
|
||||
<small
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="firstname-remote-errors"
|
||||
v-for="error in firstnameErrors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label
|
||||
for="lastname"
|
||||
class="skillboxform-input__label">Nachname</label>
|
||||
<input
|
||||
v-model="lastname"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('lastname') }"
|
||||
name="lastname"
|
||||
type="text"
|
||||
data-vv-as="Nachname"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="lastname-input"
|
||||
id="lastname"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="lastname-local-errors"
|
||||
v-if="errors.has('lastname') && submitted"
|
||||
>{{ errors.first('lastname') }}</small>
|
||||
<small
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="lastname-remote-errors"
|
||||
v-for="error in lastnameErrors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label
|
||||
for="street"
|
||||
class="skillboxform-input__label">Strasse</label>
|
||||
<input
|
||||
v-model="street"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('street') }"
|
||||
name="street"
|
||||
type="text"
|
||||
data-vv-as="Strasse"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="street-input"
|
||||
id="street"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="street-local-errors"
|
||||
v-if="errors.has('street') && submitted"
|
||||
>{{ errors.first('street') }}</small>
|
||||
<small
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="street-remote-errors"
|
||||
v-for="error in streetErrors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label
|
||||
for="city"
|
||||
class="skillboxform-input__label">Ort</label>
|
||||
<input
|
||||
v-model="city"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('city') }"
|
||||
name="city"
|
||||
type="text"
|
||||
data-vv-as="Ort"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="city-input"
|
||||
id="city"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="city-local-errors"
|
||||
v-if="errors.has('city') && submitted"
|
||||
>{{ errors.first('city') }}</small>
|
||||
<small
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="city-remote-errors"
|
||||
v-for="error in cityErrors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label
|
||||
for="postcode"
|
||||
class="skillboxform-input__label">Postleitzahl</label>
|
||||
<input
|
||||
v-model="postcode"
|
||||
v-validate="'required'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('postcode') }"
|
||||
name="postcode"
|
||||
type="text"
|
||||
data-vv-as="Postleitzahl"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="postcode-input"
|
||||
id="postcode"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="postcode-local-errors"
|
||||
v-if="errors.has('postcode') && submitted"
|
||||
>{{ errors.first('postcode') }}</small>
|
||||
<small
|
||||
:key="error"
|
||||
class="skillboxform-input__error"
|
||||
data-cy="postcode-remote-errors"
|
||||
v-for="error in postcodeErrors"
|
||||
>{{ error }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label
|
||||
for="password"
|
||||
class="skillboxform-input__label">Passwort</label>
|
||||
<input
|
||||
v-model="password"
|
||||
v-validate="'required|strongPassword'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('password') && submitted }"
|
||||
name="password"
|
||||
type="text"
|
||||
data-vv-as="Passwort"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="password-input"
|
||||
id="password"
|
||||
ref="password"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="password-local-errors"
|
||||
v-if="errors.has('password') && submitted"
|
||||
>{{ errors.first('password') }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<label
|
||||
for="password2"
|
||||
class="skillboxform-input__label">Passwort wiederholen</label>
|
||||
<input
|
||||
v-model="passwordConfirmation"
|
||||
v-validate="'required|confirmed:password'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('password-confirmation') && submitted }"
|
||||
name="password-confirmation"
|
||||
type="text"
|
||||
data-vv-as="Passwort wiederholen"
|
||||
class="change-form__new skillbox-input skillboxform-input__input"
|
||||
autocomplete="off"
|
||||
data-cy="passwordConfirmation-input"
|
||||
id="password-confirmation"
|
||||
>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="passwordConfirmation-local-errors"
|
||||
v-if="errors.has('password-confirmation') && submitted"
|
||||
>{{ errors.first('password-confirmation') }}</small>
|
||||
</div>
|
||||
<div class="change-form__field skillboxform-input">
|
||||
<checkbox
|
||||
v-model="acceptedTerms"
|
||||
:checked="acceptedTerms"
|
||||
v-validate="'required:true'"
|
||||
:class="{ 'skillboxform-input__input--error': errors.has('accepted-terms') && submitted}"
|
||||
name="accepted-terms"
|
||||
class="skillboxform-input__checkbox"
|
||||
data-cy="acceptedTerms-input"
|
||||
id="accepted-terms"
|
||||
>
|
||||
<span>Hiermit akzeptiere ich die <a
|
||||
href="https://www.hep-verlag.ch/agb"
|
||||
target="_blank"
|
||||
class="hep-link">AGB</a> und
|
||||
<a
|
||||
href="https://www.hep-verlag.ch/datenschutz"
|
||||
target="_blank"
|
||||
class="hep-link">Datenschutzbestimmungen</a></span>
|
||||
</checkbox>
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="acceptedTerms-local-errors"
|
||||
v-if="errors.has('accepted-terms') && submitted"
|
||||
>Sie müssen hier zustimmen, damit Sie sich registrieren können.</small>
|
||||
</div>
|
||||
<div class="skillboxform-input">
|
||||
<small
|
||||
class="skillboxform-input__error"
|
||||
data-cy="registration-error"
|
||||
v-if="registrationError">{{ registrationError }}</small>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<loading-button
|
||||
:loading="loading"
|
||||
class="actions__submit"
|
||||
label="Konto erstellen"
|
||||
data-cy="register-button"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {register} from '../hep-client/index';
|
||||
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import LoadingButton from '@/components/LoadingButton';
|
||||
|
||||
import pageTitleMixin from '@/mixins/page-title';
|
||||
|
||||
function initialData() {
|
||||
return {
|
||||
prefix: 'Herr',
|
||||
lastname: '',
|
||||
firstname: '',
|
||||
password: '',
|
||||
passwordConfirmation: '',
|
||||
street: '',
|
||||
postcode: '',
|
||||
city: '',
|
||||
acceptedTerms: false,
|
||||
firstnameErrors: '',
|
||||
lastnameErrors: '',
|
||||
emailErrors: '',
|
||||
passwordsErrors: [],
|
||||
passwordErrors: [],
|
||||
streetErrors: [],
|
||||
cityErrors: [],
|
||||
postcodeErrors: [],
|
||||
registrationError: '',
|
||||
acceptedTermsError: [],
|
||||
submitted: false,
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
mixins: [pageTitleMixin],
|
||||
components: {
|
||||
LoadingButton,
|
||||
Checkbox
|
||||
},
|
||||
|
||||
data() {
|
||||
return Object.assign(
|
||||
{
|
||||
helloEmail: '',
|
||||
loading: false,
|
||||
},
|
||||
initialData()
|
||||
);
|
||||
},
|
||||
|
||||
methods: {
|
||||
validateBeforeSubmit() {
|
||||
this.$validator.validate().then(result => {
|
||||
this.submitted = true;
|
||||
if (result) {
|
||||
this.loading = true;
|
||||
const registrationData = {
|
||||
customer: {
|
||||
prefix: this.prefix,
|
||||
email: this.helloEmail,
|
||||
firstname: this.firstname,
|
||||
lastname: this.lastname,
|
||||
gender: this.prefix === 'Herr' ? 1 : 2,
|
||||
addresses: [{
|
||||
street: [this.street],
|
||||
postcode: this.postcode,
|
||||
city: this.city,
|
||||
country_id: 'CH',
|
||||
firstname: this.firstname,
|
||||
lastname: this.lastname,
|
||||
prefix: this.prefix,
|
||||
default_shipping: true,
|
||||
default_billing: true
|
||||
}]
|
||||
},
|
||||
password: this.password,
|
||||
accepted_terms: this.acceptedTerms
|
||||
};
|
||||
|
||||
register(registrationData).then((response) => {
|
||||
this.loading = false;
|
||||
if (response.data.id && response.data.id > 0) {
|
||||
this.$router.push({name: 'checkEmail'});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.loading = false;
|
||||
console.warn(error);
|
||||
if (error.response.data.message) {
|
||||
switch (error.response.data.message) {
|
||||
case 'Ein Kunde mit der gleichen E-Mail-Adresse existiert bereits in einer zugeordneten Website.':
|
||||
this.emailErrors = ['Die angegebene E-Mail ist bereits registriert.'];
|
||||
break;
|
||||
case 'Sie müssen hier zustimmen, damit Sie sich registrieren können.':
|
||||
this.acceptedTermsError = ['Sie müssen hier zustimmen, damit Sie sich registrieren können.'];
|
||||
break;
|
||||
default:
|
||||
this.registrationError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
||||
}
|
||||
} else {
|
||||
this.registrationError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
resetForm() {
|
||||
Object.assign(this.$data, initialData());
|
||||
this.$validator.reset();
|
||||
}
|
||||
},
|
||||
|
||||
apollo: {
|
||||
helloEmail: {
|
||||
query: HELLO_EMAIL,
|
||||
result({data}) {
|
||||
if (data.helloEmail && data.helloEmail.email === '') {
|
||||
this.$router.push({name: 'hello'});
|
||||
}
|
||||
},
|
||||
update(data) {
|
||||
return data.helloEmail.email;
|
||||
},
|
||||
error() {
|
||||
console.log('error');
|
||||
this.$router.push({name: 'hello'});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import "@/styles/_variables.scss";
|
||||
@import "@/styles/_mixins.scss";
|
||||
|
||||
.text-link {
|
||||
font-family: $sans-serif-font-family;
|
||||
color: $color-brand;
|
||||
}
|
||||
|
||||
.actions {
|
||||
&__reset {
|
||||
display: inline-block;
|
||||
margin-left: $large-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
.registration {
|
||||
&__text {
|
||||
font-family: $sans-serif-font-family;
|
||||
margin-bottom: $small-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -1,25 +1,17 @@
|
|||
import login from '@/pages/login';
|
||||
import hello from '@/pages/hello';
|
||||
import betaLogin from '@/pages/beta-login';
|
||||
import registration from '@/pages/registration';
|
||||
import loginError from '@/pages/login-error';
|
||||
|
||||
export default [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: login,
|
||||
meta: {
|
||||
layout: 'public',
|
||||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/hello',
|
||||
name: 'hello',
|
||||
component: hello,
|
||||
meta: {
|
||||
layout: 'public',
|
||||
layout: 'split',
|
||||
public: true,
|
||||
illustration: 'hello',
|
||||
illustrationAlign: 'top'
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -32,12 +24,29 @@ export default [
|
|||
},
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
component: registration,
|
||||
name: 'registration',
|
||||
path: '/verify-email',
|
||||
component: loginError,
|
||||
name: 'verifyEmail',
|
||||
props: {
|
||||
title: 'Bitte schauen Sie in Ihr Postfach',
|
||||
errorMessage: 'Ihre E-Mail-Adresse ist noch nicht verifiziert. Wir haben eine E-Mail mit einem Aktivierungslink an Sie verschickt.'
|
||||
},
|
||||
meta: {
|
||||
public: true,
|
||||
layout: 'public',
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/unknown-auth-error',
|
||||
component: loginError,
|
||||
name: 'unknownAuthError',
|
||||
props: {
|
||||
title: 'Es ist ein Fehler aufgetreten',
|
||||
errorMessage: 'Es tut uns leid, dass mySkillbox im Moment nicht wie erwartet funktioniert. Bitte versuchen Sie es später nochmals.'
|
||||
},
|
||||
meta: {
|
||||
public: true,
|
||||
layout: 'public',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ import submission from '@/pages/studentSubmission';
|
|||
import Router from 'vue-router';
|
||||
import surveyPage from '@/pages/survey';
|
||||
import styleGuidePage from '@/pages/styleguide';
|
||||
import checkEmail from '@/pages/check-email';
|
||||
import emailVerification from '@/pages/email-verification';
|
||||
import licenseActivation from '@/pages/license-activation';
|
||||
import forgotPassword from '@/pages/forgot-password';
|
||||
import joinClass from '@/pages/joinClass';
|
||||
import news from '@/pages/news';
|
||||
|
||||
|
|
@ -25,12 +22,15 @@ import roomRoutes from './room.routes';
|
|||
|
||||
import store from '@/store/index';
|
||||
import {LAYOUT_SIMPLE} from '@/router/core.constants';
|
||||
import {EMAIL_NOT_VERIFIED_STATE, NO_VALID_LICENSE_STATE, SUCCESS_STATE} from './oauth.names';
|
||||
|
||||
const postLoginRedirectUrlKey = 'postLoginRedirectionUrl';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: start,
|
||||
component: start
|
||||
},
|
||||
...moduleRoutes,
|
||||
...authRoutes,
|
||||
|
|
@ -55,24 +55,6 @@ const routes = [
|
|||
props: true,
|
||||
meta: {layout: LAYOUT_SIMPLE},
|
||||
},
|
||||
{
|
||||
path: '/check-email',
|
||||
component: checkEmail,
|
||||
name: 'checkEmail',
|
||||
meta: {
|
||||
public: true,
|
||||
layout: 'public',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/verify-email',
|
||||
component: emailVerification,
|
||||
name: 'emailVerification',
|
||||
meta: {
|
||||
public: true,
|
||||
layout: 'public',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/license-activation',
|
||||
component: licenseActivation,
|
||||
|
|
@ -81,20 +63,31 @@ const routes = [
|
|||
layout: 'public',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/forgot-password',
|
||||
component: forgotPassword,
|
||||
name: 'forgotPassword',
|
||||
meta: {
|
||||
layout: 'public',
|
||||
public: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/news',
|
||||
component: news,
|
||||
name: 'news',
|
||||
},
|
||||
{
|
||||
path: '/oauth-redirect',
|
||||
redirect: to => {
|
||||
switch (to.query.state) {
|
||||
case EMAIL_NOT_VERIFIED_STATE:
|
||||
return '/verify-email';
|
||||
case NO_VALID_LICENSE_STATE:
|
||||
return '/license-activation';
|
||||
case SUCCESS_STATE:
|
||||
if (window.localStorage && localStorage.getItem(postLoginRedirectUrlKey)) {
|
||||
const redirectUrl = localStorage.getItem(postLoginRedirectUrlKey);
|
||||
localStorage.removeItem(postLoginRedirectUrlKey);
|
||||
return redirectUrl;
|
||||
}
|
||||
return '/';
|
||||
default:
|
||||
return '/unknown-auth-error';
|
||||
}
|
||||
}
|
||||
},
|
||||
{path: '/styleguide', component: styleGuidePage},
|
||||
{
|
||||
path: '*',
|
||||
|
|
@ -122,4 +115,5 @@ router.afterEach((to, from) => {
|
|||
store.commit('setEditModule', false);
|
||||
store.dispatch('showMobileNavigation', false);
|
||||
});
|
||||
export default router;
|
||||
|
||||
export {router, postLoginRedirectUrlKey};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
export const EMAIL_NOT_VERIFIED_STATE = 'email_not_verified';
|
||||
export const NO_VALID_LICENSE_STATE = 'no_valid_license';
|
||||
export const SUCCESS_STATE = 'success';
|
||||
|
|
@ -16,7 +16,7 @@ export default [
|
|||
component: onboardingStart,
|
||||
name: 'onboarding-start',
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
layout: 'split',
|
||||
next: ONBOARDING_STEP_1,
|
||||
},
|
||||
},
|
||||
|
|
@ -25,7 +25,7 @@ export default [
|
|||
component: onboardingStep1,
|
||||
name: ONBOARDING_STEP_1,
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
layout: 'split',
|
||||
next: ONBOARDING_STEP_2,
|
||||
illustration: 'contents',
|
||||
},
|
||||
|
|
@ -35,7 +35,7 @@ export default [
|
|||
component: onboardingStep2,
|
||||
name: ONBOARDING_STEP_2,
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
layout: 'split',
|
||||
next: ONBOARDING_STEP_3,
|
||||
illustration: 'rooms',
|
||||
},
|
||||
|
|
@ -45,7 +45,7 @@ export default [
|
|||
component: onboardingStep3,
|
||||
name: ONBOARDING_STEP_3,
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
layout: 'split',
|
||||
next: 'home',
|
||||
illustration: 'portfolio',
|
||||
},
|
||||
|
|
|
|||
24302
client/yarn.lock
24302
client/yarn.lock
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,60 @@
|
|||
{
|
||||
"name": "cariot",
|
||||
"version": "1.0.1",
|
||||
"lockfileVersion": 1,
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cariot",
|
||||
"version": "1.0.1",
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"unfetch": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "8.x"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-polyfill": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
|
||||
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"core-js": "^2.5.0",
|
||||
"regenerator-runtime": "^0.10.5"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-runtime": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
|
||||
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
|
||||
"dependencies": {
|
||||
"core-js": "^2.4.0",
|
||||
"regenerator-runtime": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-runtime/node_modules/regenerator-runtime": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
|
||||
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
|
||||
},
|
||||
"node_modules/core-js": {
|
||||
"version": "2.5.7",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
|
||||
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
|
||||
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
|
||||
},
|
||||
"node_modules/unfetch": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-3.1.1.tgz",
|
||||
"integrity": "sha512-syDl3htvM56w0HC0PTVA5jEEknOCJ3dWgWGDuaEtQUno8ORDCfZQbm12RzfWO3AC3YhWDoP61dlgmo8Z05Y97g=="
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": {
|
||||
"version": "6.26.0",
|
||||
|
|
@ -35,11 +87,6 @@
|
|||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
|
||||
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
|
||||
},
|
||||
"es6-object-assign": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz",
|
||||
"integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw="
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
|
||||
|
|
@ -49,20 +96,6 @@
|
|||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-3.1.1.tgz",
|
||||
"integrity": "sha512-syDl3htvM56w0HC0PTVA5jEEknOCJ3dWgWGDuaEtQUno8ORDCfZQbm12RzfWO3AC3YhWDoP61dlgmo8Z05Y97g=="
|
||||
},
|
||||
"vue": {
|
||||
"version": "2.6.11",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
|
||||
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ=="
|
||||
},
|
||||
"vuejs-logger": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/vuejs-logger/-/vuejs-logger-1.5.5.tgz",
|
||||
"integrity": "sha512-wESz1F4KWk98YANEDg2yeS+fpwk2WrR41ZslLfZgTD+EYFm/7VMMUjRThhHT8CCOLOCQdsS4Ge2C9bIs68v8Ww==",
|
||||
"requires": {
|
||||
"es6-object-assign": "1.1.0",
|
||||
"vue": "2.6.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ export EMAIL_HOST_PASSWORD=
|
|||
export EMAIL_HOST_USER=
|
||||
export EMAIL_PORT=
|
||||
export GOOGLE_ANALYTICS_ID=
|
||||
export HEP_ADMIN_PASSWORD=
|
||||
export HEP_ADMIN_USER=
|
||||
export HEP_URL=
|
||||
export HEP_URL=https://stage.hep-verlag.ch
|
||||
export MATOMO_HOST=
|
||||
|
|
@ -33,3 +31,9 @@ export PG_BACKUP_KEY=
|
|||
export BACKUP_AWS_ACCESS_KEY_ID=
|
||||
export BACKUP_AWS_SECRET_ACCESS_KEY=
|
||||
export BACKUP_S3_BUCKET_NAME=
|
||||
export OAUTH_CLIENT_ID=
|
||||
export OAUTH_CLIENT_SECRET=
|
||||
export OAUTH_ACCESS_TOKEN_URL=
|
||||
export OAUTH_AUTHORIZE_URL=
|
||||
export OAUTH_API_BASE_URL=
|
||||
export OAUTH_LOCAL_REDIRECT_URI=
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@ from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery
|
|||
from basicknowledge.queries import BasicKnowledgeQuery
|
||||
from books.schema.mutations import BookMutations
|
||||
from books.schema.queries import BookQuery
|
||||
from core.schema.mutations.coupon import CouponMutations
|
||||
from core.schema.mutations.main import CoreMutations
|
||||
from oauth.mutations import OauthMutations
|
||||
from notes.mutations import NoteMutations
|
||||
from objectives.mutations import ObjectiveMutations
|
||||
from objectives.schema import ObjectivesQuery
|
||||
|
|
@ -34,9 +33,9 @@ class CustomQuery(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, Objec
|
|||
debug = graphene.Field(DjangoDebug, name='_debug')
|
||||
|
||||
|
||||
class CustomMutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
|
||||
ProfileMutations, SurveyMutations, NoteMutations, SpellCheckMutations,
|
||||
CouponMutations, graphene.ObjectType):
|
||||
class CustomMutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, OauthMutations,
|
||||
PortfolioMutations, ProfileMutations, SurveyMutations, NoteMutations, SpellCheckMutations,
|
||||
graphene.ObjectType):
|
||||
if settings.DEBUG:
|
||||
debug = graphene.Field(DjangoDebug, name='_debug')
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ from graphene_django.debug import DjangoDebug
|
|||
|
||||
from news.schema_public import AllNewsTeasersQuery
|
||||
from users.mutations_public import UserMutations
|
||||
from registration.mutations_public import RegistrationMutations
|
||||
|
||||
|
||||
class PublicMutation(UserMutations, RegistrationMutations, graphene.ObjectType):
|
||||
class PublicMutation(UserMutations, graphene.ObjectType):
|
||||
|
||||
if settings.DEBUG:
|
||||
debug = graphene.Field(DjangoDebug, name='_debug')
|
||||
|
|
|
|||
|
|
@ -6,21 +6,20 @@ from graphene_django.views import GraphQLView
|
|||
|
||||
from api.schema_public import schema
|
||||
|
||||
from core.views import PrivateGraphQLView, ConfirmationKeyDisplayView
|
||||
from core.views import PrivateGraphQLView
|
||||
|
||||
app_name = 'api'
|
||||
urlpatterns = [
|
||||
url(r'^graphql-public', csrf_exempt(GraphQLView.as_view(schema=schema))),
|
||||
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view())),
|
||||
|
||||
# hep proxy
|
||||
url(r'^proxy/', include('registration.urls', namespace="registration")),
|
||||
# oauth
|
||||
url(r'^oauth/', include('oauth.urls', namespace="oauth")),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += [url(r'^graphiql-public', csrf_exempt(GraphQLView.as_view(schema=schema, graphiql=True,
|
||||
pretty=True)))]
|
||||
urlpatterns += [url(r'^graphiql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)))]
|
||||
urlpatterns += [url(r'^confirmation', ConfirmationKeyDisplayView.as_view(), name='confirmation_key_display')]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ class UserFactory(factory.django.DjangoModelFactory):
|
|||
first_name = factory.LazyAttribute(lambda x: fake.first_name())
|
||||
last_name = factory.LazyAttribute(lambda x: fake.last_name())
|
||||
email = factory.LazyAttribute(lambda x: fake.ascii_safe_email())
|
||||
hep_id = factory.Sequence(lambda n: n)
|
||||
|
||||
@factory.post_generation
|
||||
def post(self, create, extracted, **kwargs):
|
||||
|
|
|
|||
|
|
@ -1,236 +0,0 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
import logging
|
||||
import requests
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TEACHER_KEY = 'teacher'
|
||||
STUDENT_KEY = 'student'
|
||||
|
||||
MYSKILLBOX_LICENSES = {
|
||||
"978-3-0355-1397-4": {
|
||||
'edition': STUDENT_KEY,
|
||||
'duration': 4 * 365,
|
||||
'name': 'Student 4 years'
|
||||
},
|
||||
"978-3-0355-1860-3": {
|
||||
'edition': STUDENT_KEY,
|
||||
'duration': 455,
|
||||
'name': 'Student 1 year'
|
||||
},
|
||||
"978-3-0355-1862-7": {
|
||||
'edition': STUDENT_KEY,
|
||||
'duration': 30,
|
||||
'name': 'Student test 1 month'
|
||||
},
|
||||
"978-3-0355-1861-0": {
|
||||
'edition': TEACHER_KEY,
|
||||
'duration': 30,
|
||||
'name': 'Teacher test 1 month'
|
||||
},
|
||||
"978-3-0355-1823-8": {
|
||||
'edition': TEACHER_KEY,
|
||||
'duration': 455,
|
||||
'name': 'Teacher 1 year'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class HepClientException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HepClientUnauthorizedException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HepClient:
|
||||
URL = settings.HEP_URL
|
||||
WEBSITE_ID = 1
|
||||
HEADERS = {
|
||||
'accept': 'application/json',
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
|
||||
def _call(self, url, method='get', data=None, additional_headers=None):
|
||||
|
||||
request_url = f'{self.URL}{url}'
|
||||
|
||||
if additional_headers:
|
||||
headers = {**additional_headers, **self.HEADERS}
|
||||
else:
|
||||
headers = self.HEADERS
|
||||
|
||||
if method == 'post':
|
||||
response = requests.post(request_url, json=data, headers=headers)
|
||||
elif method == 'get':
|
||||
if data:
|
||||
response = requests.get(request_url, headers=headers, data=data)
|
||||
else:
|
||||
response = requests.get(request_url, headers=headers)
|
||||
elif method == 'put':
|
||||
response = requests.put(request_url, data=data)
|
||||
|
||||
# Todo handle 401 and most important network errors
|
||||
if response.status_code == 401:
|
||||
raise HepClientUnauthorizedException(response.status_code, response.json())
|
||||
elif response.status_code != 200:
|
||||
raise HepClientException(response.status_code, response.json())
|
||||
|
||||
return response
|
||||
|
||||
def fetch_admin_token(self, admin_user, password):
|
||||
response = self._call('/rest/deutsch/V1/integration/admin/token', 'post',
|
||||
data={'username': admin_user, 'password': password})
|
||||
return response.content.decode('utf-8')[1:-1]
|
||||
|
||||
def is_email_available(self, email):
|
||||
response = self._call('/rest/deutsch/V1/customers/isEmailAvailable', method='post',
|
||||
data={'customerEmail': email, 'websiteId': self.WEBSITE_ID})
|
||||
return response.json()
|
||||
|
||||
def is_email_verified(self, user_data):
|
||||
return 'confirmation' not in user_data
|
||||
|
||||
def customer_verify_email(self, confirmation_key):
|
||||
response = self._call('/rest/V1/customers/me', method='put', data={'confirmationKey': confirmation_key})
|
||||
return response.json()
|
||||
|
||||
def customer_create(self, customer_data):
|
||||
response = self._call('/rest/deutsch/V1/customers', method='post', data=customer_data)
|
||||
return response.json()
|
||||
|
||||
def customer_token(self, username, password):
|
||||
response = self._call('/rest/deutsch/V1/integration/customer/token', 'post',
|
||||
data={'username': username, 'password': password})
|
||||
return response.json()
|
||||
|
||||
def customer_me(self, token):
|
||||
response = self._call('/rest/V1/customers/me', additional_headers={'authorization': f'Bearer {token}'})
|
||||
return response.json()
|
||||
|
||||
def customer_activate(self, confirmation_key, user_id):
|
||||
response = self._call(f'/customer/account/confirm/?back_url=&id={user_id}&key={confirmation_key}', method='get')
|
||||
return response
|
||||
|
||||
def customers_search(self, admin_token, email):
|
||||
response = self._call('/rest/V1/customers/search?searchCriteria[filterGroups][0][filters][0][field]='
|
||||
f'email&searchCriteria[filterGroups][0][filters][0][value]={email}',
|
||||
additional_headers={'authorization': f'Bearer {admin_token}'})
|
||||
|
||||
json_data = response.json()
|
||||
if len(json_data['items']) > 0:
|
||||
return json_data['items'][0]
|
||||
return None
|
||||
|
||||
def customers_by_id(self, admin_token, user_id):
|
||||
response = self._call('/rest/V1/customers/{}'.format(user_id),
|
||||
additional_headers={'authorization': f'Bearer {admin_token}'})
|
||||
return response.json()
|
||||
|
||||
def _customer_orders(self, admin_token, customer_id):
|
||||
url = ('/rest/V1/orders/?searchCriteria[filterGroups][0][filters][0]['
|
||||
f'field]=customer_id&searchCriteria[filterGroups][0][filters][0][value]={customer_id}')
|
||||
|
||||
response = self._call(url, additional_headers={'authorization': 'Bearer {}'.format(admin_token)})
|
||||
return response.json()
|
||||
|
||||
def coupon_redeem(self, coupon, customer_id):
|
||||
try:
|
||||
response = self._call(f'/rest/deutsch/V1/coupon/{coupon}/customer/{customer_id}', method='put')
|
||||
except HepClientException:
|
||||
return None
|
||||
|
||||
response_data = response.json()
|
||||
if response_data[0] == '201':
|
||||
return None
|
||||
|
||||
return response_data[0]
|
||||
|
||||
def myskillbox_product_for_customer(self, admin_token, customer_id):
|
||||
orders = self._customer_orders(admin_token, customer_id)
|
||||
products = self._extract_myskillbox_products(orders)
|
||||
|
||||
if len(products) == 0:
|
||||
return None
|
||||
else:
|
||||
return self._get_relevant_product(products)
|
||||
|
||||
def _extract_myskillbox_products(self, orders):
|
||||
products = []
|
||||
|
||||
for order_item in orders['items']:
|
||||
|
||||
status = ''
|
||||
if 'status' in order_item:
|
||||
status = order_item['status']
|
||||
|
||||
for item in order_item['items']:
|
||||
|
||||
order_id = -1
|
||||
if 'order_id' in item:
|
||||
order_id = item['order_id']
|
||||
|
||||
if item['sku'] in list(MYSKILLBOX_LICENSES.keys()):
|
||||
product = {
|
||||
'raw': item,
|
||||
'activated': self._get_item_activation(order_item),
|
||||
'status': status,
|
||||
'order_id': order_id,
|
||||
'license': MYSKILLBOX_LICENSES[item['sku']],
|
||||
'isbn': item['sku']
|
||||
}
|
||||
|
||||
products.append(product)
|
||||
|
||||
return products
|
||||
|
||||
def _get_item_activation(self, item):
|
||||
if 'created_at' in item:
|
||||
return datetime.strptime(item['created_at'], '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
def _get_relevant_product(self, products):
|
||||
|
||||
def filter_valid_products(product):
|
||||
|
||||
if product['status'] != 'complete':
|
||||
return False
|
||||
|
||||
expiry_delta = product['activated'] + timedelta(product['license']['duration'])
|
||||
|
||||
if HepClient.is_product_active(expiry_delta, product['isbn']):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
active_products = list(filter(filter_valid_products, products))
|
||||
|
||||
if len(active_products) == 0:
|
||||
return None
|
||||
elif len(active_products) == 1:
|
||||
return active_products[0]
|
||||
else:
|
||||
return self._select_from_teacher_products(active_products)
|
||||
|
||||
def _select_from_teacher_products(self, active_products):
|
||||
teacher_edition = None
|
||||
|
||||
# select first teacher product, as they are all valid it does not matter which one
|
||||
for product in active_products:
|
||||
if product['license']['edition'] == TEACHER_KEY:
|
||||
teacher_edition = product
|
||||
break
|
||||
|
||||
# select a student product, as they are all valid it does not matter which one
|
||||
if not teacher_edition:
|
||||
return active_products[0]
|
||||
|
||||
return teacher_edition
|
||||
|
||||
@staticmethod
|
||||
def is_product_active(expiry_date, isbn):
|
||||
now = datetime.now()
|
||||
|
||||
return expiry_date >= now >= expiry_date - timedelta(days=MYSKILLBOX_LICENSES[isbn]['duration'])
|
||||
|
|
@ -12,7 +12,6 @@ from wagtail.core.models import Page
|
|||
from books.factories import BookFactory, TopicFactory, ModuleFactory, ChapterFactory, ContentBlockFactory
|
||||
from core.factories import UserFactory
|
||||
from objectives.factories import ObjectiveGroupFactory, ObjectiveFactory
|
||||
from users.models import Role
|
||||
from users.services import create_users, create_student
|
||||
|
||||
from .data.module_data import data
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from core.models import AdminData
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"Update admin token via cronjob"
|
||||
AdminData.objects.update_admin_token()
|
||||
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
from django.conf import settings
|
||||
from django.db import models
|
||||
from datetime import timedelta
|
||||
from django.utils import timezone
|
||||
|
||||
from core.hep_client import HepClient
|
||||
|
||||
DEFAULT_PK = 1
|
||||
|
||||
|
||||
class AdminDataManager(models.Manager):
|
||||
hep_client = HepClient()
|
||||
|
||||
def update_admin_token(self):
|
||||
admin_token = self.hep_client.fetch_admin_token(settings.HEP_ADMIN_USER, settings.HEP_ADMIN_PASSWORD)
|
||||
|
||||
admin_data, created = self.get_or_create(pk=DEFAULT_PK)
|
||||
admin_data.hep_admin_token = admin_token
|
||||
admin_data.save()
|
||||
return admin_data.hep_admin_token
|
||||
|
||||
def get_admin_token(self):
|
||||
try:
|
||||
admin_token = self.get(pk=DEFAULT_PK)
|
||||
if admin_token.updated_at < timezone.now() + timedelta(hours=1):
|
||||
admin_token = self.update_admin_token()
|
||||
except self.model.DoesNotExist:
|
||||
admin_token = self.update_admin_token()
|
||||
|
||||
return admin_token
|
||||
|
|
@ -1,12 +1,10 @@
|
|||
import json
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponsePermanentRedirect, HttpResponse
|
||||
from django.http import Http404, HttpResponsePermanentRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from core.utils import is_private_api_call_allowed
|
||||
|
||||
try:
|
||||
from threading import local
|
||||
|
|
@ -99,12 +97,3 @@ class UserLoggedInCookieMiddleWare(MiddlewareMixin):
|
|||
response.delete_cookie(self.cookie_name)
|
||||
return response
|
||||
|
||||
|
||||
class UserHasLicenseMiddleWare(MiddlewareMixin):
|
||||
|
||||
def process_response(self, request, response):
|
||||
if request.path == '/api/graphql/':
|
||||
if not is_private_api_call_allowed(request.user, request.body):
|
||||
return HttpResponse(json.dumps({'errors': ['no active license']}), status=402)
|
||||
|
||||
return response
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
|
||||
from core.managers import AdminDataManager
|
||||
|
||||
|
||||
class AdminData(models.Model):
|
||||
hep_admin_token = models.CharField(max_length=100, blank=False, null=False)
|
||||
updated_at = models.DateTimeField(blank=False, null=True, auto_now=True)
|
||||
|
||||
objects = AdminDataManager()
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import graphene
|
||||
from graphene import relay
|
||||
|
||||
from core.hep_client import HepClient, HepClientException
|
||||
from users.user_signup_login_handler import check_and_create_licenses, create_role_for_user
|
||||
|
||||
|
||||
class Coupon(relay.ClientIDMutation):
|
||||
class Input:
|
||||
coupon_code = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
coupon_code = kwargs.get('coupon_code').strip()
|
||||
hep_client = HepClient()
|
||||
|
||||
try:
|
||||
hep_id = info.context.user.hep_id
|
||||
except AttributeError:
|
||||
raise Exception('not_authenticated')
|
||||
|
||||
try:
|
||||
response = hep_client.coupon_redeem(coupon_code, hep_id)
|
||||
except HepClientException:
|
||||
raise Exception('unknown_error')
|
||||
|
||||
if not response:
|
||||
raise Exception('invalid_coupon')
|
||||
|
||||
license, error_msg = check_and_create_licenses(hep_client, info.context.user)
|
||||
|
||||
# todo fail if no license
|
||||
if error_msg:
|
||||
raise Exception(error_msg)
|
||||
|
||||
create_role_for_user(info.context.user, license.for_role.key)
|
||||
|
||||
return cls(success=True)
|
||||
|
||||
|
||||
class CouponMutations:
|
||||
redeem_coupon = Coupon.Field()
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2018 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 22.10.18
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
|
||||
import graphene
|
||||
from django.contrib.auth import logout
|
||||
|
||||
|
||||
class Logout(graphene.Mutation):
|
||||
success = graphene.Boolean()
|
||||
|
||||
def mutate(self, info, **kwargs):
|
||||
try:
|
||||
logout(info.context)
|
||||
return Logout(success=True)
|
||||
except Exception:
|
||||
return Logout(success=False)
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2018 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 22.10.18
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from core.schema.mutations.coupon import Coupon
|
||||
from core.schema.mutations.logout import Logout
|
||||
|
||||
|
||||
class CoreMutations(object):
|
||||
logout = Logout.Field()
|
||||
coupon = Coupon.Field()
|
||||
|
|
@ -59,8 +59,8 @@ INSTALLED_APPS = [
|
|||
'statistics',
|
||||
'surveys',
|
||||
'notes',
|
||||
'registration',
|
||||
'news',
|
||||
'oauth',
|
||||
|
||||
'wagtail.contrib.forms',
|
||||
'wagtail.contrib.redirects',
|
||||
|
|
@ -131,7 +131,7 @@ MIDDLEWARE += [
|
|||
'core.middleware.ThreadLocalMiddleware',
|
||||
'core.middleware.CommonRedirectMiddleware',
|
||||
'core.middleware.UserLoggedInCookieMiddleWare',
|
||||
'core.middleware.UserHasLicenseMiddleWare',
|
||||
'oauth.middleware.user_has_license_middleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'core.urls'
|
||||
|
|
@ -397,10 +397,29 @@ EMAIL_USE_SSL = False
|
|||
ALLOW_BETA_LOGIN = True
|
||||
|
||||
# HEP
|
||||
HEP_ADMIN_USER = os.environ.get("HEP_ADMIN_USER")
|
||||
HEP_ADMIN_PASSWORD = os.environ.get("HEP_ADMIN_PASSWORD")
|
||||
HEP_URL = os.environ.get("HEP_URL")
|
||||
HEP_MYSKILLBOX_GROUP_ID = 5
|
||||
|
||||
# HEP Oauth
|
||||
AUTHLIB_OAUTH_CLIENTS = {
|
||||
'hep': {
|
||||
'client_id': os.environ.get("OAUTH_CLIENT_ID"),
|
||||
'client_secret': os.environ.get("OAUTH_CLIENT_SECRET"),
|
||||
'request_token_url': None,
|
||||
'request_token_params': None,
|
||||
'access_token_url': os.environ.get("OAUTH_ACCESS_TOKEN_URL"),
|
||||
'access_token_params': None,
|
||||
'refresh_token_url': None,
|
||||
'authorize_url': os.environ.get("OAUTH_AUTHORIZE_URL"),
|
||||
'api_base_url': os.environ.get("OAUTH_API_BASE_URL"),
|
||||
'client_kwargs': {
|
||||
'scope': 'orders',
|
||||
'token_endpoint_auth_method': 'client_secret_post',
|
||||
'token_placement': 'header',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OAUTH_LOCAL_REDIRECT_URI = os.environ.get("OAUTH_LOCAL_REDIRECT_URI")
|
||||
|
||||
TASKBASE_USER = os.environ.get("TASKBASE_USER")
|
||||
TASKBASE_PASSWORD = os.environ.get("TASKBASE_PASSWORD")
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"id": 49124,
|
||||
"group_id": 1,
|
||||
"default_billing": "47579",
|
||||
"default_shipping": "47579",
|
||||
"created_at": "2018-07-19 15:05:27",
|
||||
"updated_at": "2019-11-26 17:04:29",
|
||||
"created_in": "hep verlag",
|
||||
"email": "1heptest19072018@mailinator.com",
|
||||
"firstname": "Test",
|
||||
"lastname": "Test",
|
||||
"prefix": "Frau",
|
||||
"gender": 2,
|
||||
"store_id": 1,
|
||||
"website_id": 1,
|
||||
"addresses": [
|
||||
{
|
||||
"id": 47579,
|
||||
"customer_id": 49124,
|
||||
"region": {
|
||||
"region_code": null,
|
||||
"region": null,
|
||||
"region_id": 0
|
||||
},
|
||||
"region_id": 0,
|
||||
"country_id": "CH",
|
||||
"street": [
|
||||
"Test"
|
||||
],
|
||||
"telephone": "",
|
||||
"postcode": "0000",
|
||||
"city": "Test",
|
||||
"firstname": "Test",
|
||||
"lastname": "Test",
|
||||
"prefix": "Frau",
|
||||
"default_shipping": true,
|
||||
"default_billing": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,526 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
{
|
||||
"base_currency_code": "CHF",
|
||||
"base_discount_amount": 0,
|
||||
"base_grand_total": 46,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"base_subtotal": 44.88,
|
||||
"base_subtotal_incl_tax": 46,
|
||||
"base_tax_amount": 1.12,
|
||||
"base_total_due": 46,
|
||||
"base_to_global_rate": 1,
|
||||
"base_to_order_rate": 1,
|
||||
"billing_address_id": 83693,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"customer_email": "1heptest19072018@mailinator.com",
|
||||
"customer_firstname": "Test",
|
||||
"customer_gender": 2,
|
||||
"customer_group_id": 4,
|
||||
"customer_id": 49124,
|
||||
"customer_is_guest": 0,
|
||||
"customer_lastname": "Test",
|
||||
"customer_note": "coupon",
|
||||
"customer_note_notify": 1,
|
||||
"customer_prefix": "Frau",
|
||||
"discount_amount": 0,
|
||||
"email_sent": 1,
|
||||
"entity_id": 57612,
|
||||
"global_currency_code": "CHF",
|
||||
"grand_total": 46,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"increment_id": "1004614768",
|
||||
"is_virtual": 1,
|
||||
"order_currency_code": "CHF",
|
||||
"protect_code": "71aedb",
|
||||
"quote_id": 104401,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0,
|
||||
"state": "complete",
|
||||
"status": "complete",
|
||||
"store_currency_code": "CHF",
|
||||
"store_id": 1,
|
||||
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||
"store_to_base_rate": 0,
|
||||
"store_to_order_rate": 0,
|
||||
"subtotal": 44.88,
|
||||
"subtotal_incl_tax": 46,
|
||||
"tax_amount": 1.12,
|
||||
"total_due": 46,
|
||||
"total_item_count": 1,
|
||||
"total_qty_ordered": 1,
|
||||
"updated_at": "2018-07-19 15:05:33",
|
||||
"weight": 0,
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 46,
|
||||
"base_price": 44.88,
|
||||
"base_price_incl_tax": 46,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 44.88,
|
||||
"base_row_total_incl_tax": 46,
|
||||
"base_tax_amount": 1.12,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80317,
|
||||
"name": "Myskillbox Schüler Edition",
|
||||
"no_discount": 0,
|
||||
"order_id": 57612,
|
||||
"original_price": 46,
|
||||
"price": 44.88,
|
||||
"price_incl_tax": 46,
|
||||
"product_id": 8652,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135166,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 44.88,
|
||||
"row_total_incl_tax": 46,
|
||||
"sku": "978-3-0355-1397-4",
|
||||
"store_id": 1,
|
||||
"tax_amount": 1.12,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:05:33",
|
||||
"weight": 0.01
|
||||
}
|
||||
],
|
||||
"billing_address": {
|
||||
"address_type": "billing",
|
||||
"city": "Test",
|
||||
"country_id": "CH",
|
||||
"customer_address_id": 47579,
|
||||
"email": "1heptest19072018@mailinator.com",
|
||||
"entity_id": 83693,
|
||||
"firstname": "Test",
|
||||
"lastname": "Test",
|
||||
"parent_id": 57612,
|
||||
"postcode": "0000",
|
||||
"prefix": "Frau",
|
||||
"street": [
|
||||
"Test"
|
||||
],
|
||||
"telephone": null
|
||||
},
|
||||
"payment": {
|
||||
"account_status": null,
|
||||
"additional_information": [
|
||||
"Rechnung",
|
||||
null,
|
||||
null
|
||||
],
|
||||
"amount_ordered": 46,
|
||||
"base_amount_ordered": 46,
|
||||
"base_shipping_amount": 0,
|
||||
"cc_last4": null,
|
||||
"entity_id": 57612,
|
||||
"method": "checkmo",
|
||||
"parent_id": 57612,
|
||||
"shipping_amount": 0,
|
||||
"extension_attributes": []
|
||||
},
|
||||
"status_histories": [
|
||||
{
|
||||
"comment": "payed by couponcode",
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244885,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "licence-coupon \"ebf81a59b968\"",
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244884,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": null,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244883,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "Exported to ERP",
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244882,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
}
|
||||
],
|
||||
"extension_attributes": {
|
||||
"shipping_assignments": [
|
||||
{
|
||||
"shipping": {
|
||||
"total": {
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0
|
||||
}
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 46,
|
||||
"base_price": 44.88,
|
||||
"base_price_incl_tax": 46,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 44.88,
|
||||
"base_row_total_incl_tax": 46,
|
||||
"base_tax_amount": 1.12,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80317,
|
||||
"name": "Gesellschaft Ausgabe A (eLehrmittel, Neuauflage)",
|
||||
"no_discount": 0,
|
||||
"order_id": 57612,
|
||||
"original_price": 46,
|
||||
"price": 44.88,
|
||||
"price_incl_tax": 46,
|
||||
"product_id": 8652,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135166,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 44.88,
|
||||
"row_total_incl_tax": 46,
|
||||
"sku": "978-3-0355-1082-9",
|
||||
"store_id": 1,
|
||||
"tax_amount": 1.12,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:05:33",
|
||||
"weight": 0.01
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"base_currency_code": "CHF",
|
||||
"base_discount_amount": 0,
|
||||
"base_grand_total": 24,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"base_subtotal": 23.41,
|
||||
"base_subtotal_incl_tax": 24,
|
||||
"base_tax_amount": 0.59,
|
||||
"base_total_due": 24,
|
||||
"base_to_global_rate": 1,
|
||||
"base_to_order_rate": 1,
|
||||
"billing_address_id": 83696,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"customer_email": "1heptest19072018@mailinator.com",
|
||||
"customer_firstname": "Test",
|
||||
"customer_gender": 2,
|
||||
"customer_group_id": 4,
|
||||
"customer_id": 49124,
|
||||
"customer_is_guest": 0,
|
||||
"customer_lastname": "Test",
|
||||
"customer_note": "coupon",
|
||||
"customer_note_notify": 1,
|
||||
"customer_prefix": "Frau",
|
||||
"discount_amount": 0,
|
||||
"email_sent": 1,
|
||||
"entity_id": 57614,
|
||||
"global_currency_code": "CHF",
|
||||
"grand_total": 24,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"increment_id": "1004614770",
|
||||
"is_virtual": 1,
|
||||
"order_currency_code": "CHF",
|
||||
"protect_code": "1a88e9",
|
||||
"quote_id": 104403,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0,
|
||||
"state": "complete",
|
||||
"status": "complete",
|
||||
"store_currency_code": "CHF",
|
||||
"store_id": 1,
|
||||
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||
"store_to_base_rate": 0,
|
||||
"store_to_order_rate": 0,
|
||||
"subtotal": 23.41,
|
||||
"subtotal_incl_tax": 24,
|
||||
"tax_amount": 0.59,
|
||||
"total_due": 24,
|
||||
"total_item_count": 1,
|
||||
"total_qty_ordered": 1,
|
||||
"updated_at": "2018-07-19 15:19:00",
|
||||
"weight": 0,
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 24,
|
||||
"base_price": 23.41,
|
||||
"base_price_incl_tax": 24,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 23.41,
|
||||
"base_row_total_incl_tax": 24,
|
||||
"base_tax_amount": 0.59,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80320,
|
||||
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||
"no_discount": 0,
|
||||
"order_id": 57614,
|
||||
"original_price": 24,
|
||||
"price": 23.41,
|
||||
"price_incl_tax": 24,
|
||||
"product_id": 8654,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135169,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 23.41,
|
||||
"row_total_incl_tax": 24,
|
||||
"sku": "978-3-0355-1185-7",
|
||||
"store_id": 1,
|
||||
"tax_amount": 0.59,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:19:00",
|
||||
"weight": 0.01
|
||||
}
|
||||
],
|
||||
"billing_address": {
|
||||
"address_type": "billing",
|
||||
"city": "Test",
|
||||
"country_id": "CH",
|
||||
"customer_address_id": 47579,
|
||||
"email": "1heptest19072018@mailinator.com",
|
||||
"entity_id": 83696,
|
||||
"firstname": "Test",
|
||||
"lastname": "Test",
|
||||
"parent_id": 57614,
|
||||
"postcode": "0000",
|
||||
"prefix": "Frau",
|
||||
"street": [
|
||||
"Test"
|
||||
],
|
||||
"telephone": null
|
||||
},
|
||||
"payment": {
|
||||
"account_status": null,
|
||||
"additional_information": [
|
||||
"Rechnung",
|
||||
null,
|
||||
null
|
||||
],
|
||||
"amount_ordered": 24,
|
||||
"base_amount_ordered": 24,
|
||||
"base_shipping_amount": 0,
|
||||
"cc_last4": null,
|
||||
"entity_id": 57614,
|
||||
"method": "checkmo",
|
||||
"parent_id": 57614,
|
||||
"shipping_amount": 0,
|
||||
"extension_attributes": []
|
||||
},
|
||||
"status_histories": [
|
||||
{
|
||||
"comment": "payed by couponcode",
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244890,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "licence-coupon \"ece5e74a2b36\"",
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244889,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": null,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244888,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "Exported to ERP",
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244887,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
}
|
||||
],
|
||||
"extension_attributes": {
|
||||
"shipping_assignments": [
|
||||
{
|
||||
"shipping": {
|
||||
"total": {
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0
|
||||
}
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 24,
|
||||
"base_price": 23.41,
|
||||
"base_price_incl_tax": 24,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 23.41,
|
||||
"base_row_total_incl_tax": 24,
|
||||
"base_tax_amount": 0.59,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80320,
|
||||
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||
"no_discount": 0,
|
||||
"order_id": 57614,
|
||||
"original_price": 24,
|
||||
"price": 23.41,
|
||||
"price_incl_tax": 24,
|
||||
"product_id": 8654,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135169,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 23.41,
|
||||
"row_total_incl_tax": 24,
|
||||
"sku": "978-3-0355-1185-7",
|
||||
"store_id": 1,
|
||||
"tax_amount": 0.59,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:19:00",
|
||||
"weight": 0.01
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"search_criteria": {
|
||||
"filter_groups": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"field": "customer_id",
|
||||
"value": "49124",
|
||||
"condition_type": "eq"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"total_count": 2
|
||||
}
|
||||
|
|
@ -1,526 +0,0 @@
|
|||
{
|
||||
"items": [
|
||||
{
|
||||
"base_currency_code": "CHF",
|
||||
"base_discount_amount": 0,
|
||||
"base_grand_total": 46,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"base_subtotal": 44.88,
|
||||
"base_subtotal_incl_tax": 46,
|
||||
"base_tax_amount": 1.12,
|
||||
"base_total_due": 46,
|
||||
"base_to_global_rate": 1,
|
||||
"base_to_order_rate": 1,
|
||||
"billing_address_id": 83693,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"customer_email": "1heptest19072018@mailinator.com",
|
||||
"customer_firstname": "Test",
|
||||
"customer_gender": 2,
|
||||
"customer_group_id": 4,
|
||||
"customer_id": 49124,
|
||||
"customer_is_guest": 0,
|
||||
"customer_lastname": "Test",
|
||||
"customer_note": "coupon",
|
||||
"customer_note_notify": 1,
|
||||
"customer_prefix": "Frau",
|
||||
"discount_amount": 0,
|
||||
"email_sent": 1,
|
||||
"entity_id": 57612,
|
||||
"global_currency_code": "CHF",
|
||||
"grand_total": 46,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"increment_id": "1004614768",
|
||||
"is_virtual": 1,
|
||||
"order_currency_code": "CHF",
|
||||
"protect_code": "71aedb",
|
||||
"quote_id": 104401,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0,
|
||||
"state": "complete",
|
||||
"status": "complete",
|
||||
"store_currency_code": "CHF",
|
||||
"store_id": 1,
|
||||
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||
"store_to_base_rate": 0,
|
||||
"store_to_order_rate": 0,
|
||||
"subtotal": 44.88,
|
||||
"subtotal_incl_tax": 46,
|
||||
"tax_amount": 1.12,
|
||||
"total_due": 46,
|
||||
"total_item_count": 1,
|
||||
"total_qty_ordered": 1,
|
||||
"updated_at": "2018-07-19 15:05:33",
|
||||
"weight": 0,
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 46,
|
||||
"base_price": 44.88,
|
||||
"base_price_incl_tax": 46,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 44.88,
|
||||
"base_row_total_incl_tax": 46,
|
||||
"base_tax_amount": 1.12,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80317,
|
||||
"name": "Myskillbox Lehreredition",
|
||||
"no_discount": 0,
|
||||
"order_id": 57612,
|
||||
"original_price": 46,
|
||||
"price": 44.88,
|
||||
"price_incl_tax": 46,
|
||||
"product_id": 8652,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135166,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 44.88,
|
||||
"row_total_incl_tax": 46,
|
||||
"sku": "978-3-0355-1823-8",
|
||||
"store_id": 1,
|
||||
"tax_amount": 1.12,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:05:33",
|
||||
"weight": 0.01
|
||||
}
|
||||
],
|
||||
"billing_address": {
|
||||
"address_type": "billing",
|
||||
"city": "Test",
|
||||
"country_id": "CH",
|
||||
"customer_address_id": 47579,
|
||||
"email": "1heptest19072018@mailinator.com",
|
||||
"entity_id": 83693,
|
||||
"firstname": "Test",
|
||||
"lastname": "Test",
|
||||
"parent_id": 57612,
|
||||
"postcode": "0000",
|
||||
"prefix": "Frau",
|
||||
"street": [
|
||||
"Test"
|
||||
],
|
||||
"telephone": null
|
||||
},
|
||||
"payment": {
|
||||
"account_status": null,
|
||||
"additional_information": [
|
||||
"Rechnung",
|
||||
null,
|
||||
null
|
||||
],
|
||||
"amount_ordered": 46,
|
||||
"base_amount_ordered": 46,
|
||||
"base_shipping_amount": 0,
|
||||
"cc_last4": null,
|
||||
"entity_id": 57612,
|
||||
"method": "checkmo",
|
||||
"parent_id": 57612,
|
||||
"shipping_amount": 0,
|
||||
"extension_attributes": []
|
||||
},
|
||||
"status_histories": [
|
||||
{
|
||||
"comment": "payed by couponcode",
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244885,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "licence-coupon \"ebf81a59b968\"",
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244884,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": null,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244883,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "Exported to ERP",
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"entity_id": 244882,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57612,
|
||||
"status": "complete"
|
||||
}
|
||||
],
|
||||
"extension_attributes": {
|
||||
"shipping_assignments": [
|
||||
{
|
||||
"shipping": {
|
||||
"total": {
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0
|
||||
}
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 46,
|
||||
"base_price": 44.88,
|
||||
"base_price_incl_tax": 46,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 44.88,
|
||||
"base_row_total_incl_tax": 46,
|
||||
"base_tax_amount": 1.12,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:05:33",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80317,
|
||||
"name": "Gesellschaft Ausgabe A (eLehrmittel, Neuauflage)",
|
||||
"no_discount": 0,
|
||||
"order_id": 57612,
|
||||
"original_price": 46,
|
||||
"price": 44.88,
|
||||
"price_incl_tax": 46,
|
||||
"product_id": 8652,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135166,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 44.88,
|
||||
"row_total_incl_tax": 46,
|
||||
"sku": "978-3-0355-1082-9",
|
||||
"store_id": 1,
|
||||
"tax_amount": 1.12,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:05:33",
|
||||
"weight": 0.01
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"base_currency_code": "CHF",
|
||||
"base_discount_amount": 0,
|
||||
"base_grand_total": 24,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"base_subtotal": 23.41,
|
||||
"base_subtotal_incl_tax": 24,
|
||||
"base_tax_amount": 0.59,
|
||||
"base_total_due": 24,
|
||||
"base_to_global_rate": 1,
|
||||
"base_to_order_rate": 1,
|
||||
"billing_address_id": 83696,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"customer_email": "1heptest19072018@mailinator.com",
|
||||
"customer_firstname": "Test",
|
||||
"customer_gender": 2,
|
||||
"customer_group_id": 4,
|
||||
"customer_id": 49124,
|
||||
"customer_is_guest": 0,
|
||||
"customer_lastname": "Test",
|
||||
"customer_note": "coupon",
|
||||
"customer_note_notify": 1,
|
||||
"customer_prefix": "Frau",
|
||||
"discount_amount": 0,
|
||||
"email_sent": 1,
|
||||
"entity_id": 57614,
|
||||
"global_currency_code": "CHF",
|
||||
"grand_total": 24,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"increment_id": "1004614770",
|
||||
"is_virtual": 1,
|
||||
"order_currency_code": "CHF",
|
||||
"protect_code": "1a88e9",
|
||||
"quote_id": 104403,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0,
|
||||
"state": "complete",
|
||||
"status": "complete",
|
||||
"store_currency_code": "CHF",
|
||||
"store_id": 1,
|
||||
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||
"store_to_base_rate": 0,
|
||||
"store_to_order_rate": 0,
|
||||
"subtotal": 23.41,
|
||||
"subtotal_incl_tax": 24,
|
||||
"tax_amount": 0.59,
|
||||
"total_due": 24,
|
||||
"total_item_count": 1,
|
||||
"total_qty_ordered": 1,
|
||||
"updated_at": "2018-07-19 15:19:00",
|
||||
"weight": 0,
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 24,
|
||||
"base_price": 23.41,
|
||||
"base_price_incl_tax": 24,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 23.41,
|
||||
"base_row_total_incl_tax": 24,
|
||||
"base_tax_amount": 0.59,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80320,
|
||||
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||
"no_discount": 0,
|
||||
"order_id": 57614,
|
||||
"original_price": 24,
|
||||
"price": 23.41,
|
||||
"price_incl_tax": 24,
|
||||
"product_id": 8654,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135169,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 23.41,
|
||||
"row_total_incl_tax": 24,
|
||||
"sku": "978-3-0355-1185-7",
|
||||
"store_id": 1,
|
||||
"tax_amount": 0.59,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:19:00",
|
||||
"weight": 0.01
|
||||
}
|
||||
],
|
||||
"billing_address": {
|
||||
"address_type": "billing",
|
||||
"city": "Test",
|
||||
"country_id": "CH",
|
||||
"customer_address_id": 47579,
|
||||
"email": "1heptest19072018@mailinator.com",
|
||||
"entity_id": 83696,
|
||||
"firstname": "Test",
|
||||
"lastname": "Test",
|
||||
"parent_id": 57614,
|
||||
"postcode": "0000",
|
||||
"prefix": "Frau",
|
||||
"street": [
|
||||
"Test"
|
||||
],
|
||||
"telephone": null
|
||||
},
|
||||
"payment": {
|
||||
"account_status": null,
|
||||
"additional_information": [
|
||||
"Rechnung",
|
||||
null,
|
||||
null
|
||||
],
|
||||
"amount_ordered": 24,
|
||||
"base_amount_ordered": 24,
|
||||
"base_shipping_amount": 0,
|
||||
"cc_last4": null,
|
||||
"entity_id": 57614,
|
||||
"method": "checkmo",
|
||||
"parent_id": 57614,
|
||||
"shipping_amount": 0,
|
||||
"extension_attributes": []
|
||||
},
|
||||
"status_histories": [
|
||||
{
|
||||
"comment": "payed by couponcode",
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244890,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "licence-coupon \"ece5e74a2b36\"",
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244889,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": null,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": null,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244888,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
},
|
||||
{
|
||||
"comment": "Exported to ERP",
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"entity_id": 244887,
|
||||
"entity_name": "order",
|
||||
"is_customer_notified": 0,
|
||||
"is_visible_on_front": 0,
|
||||
"parent_id": 57614,
|
||||
"status": "complete"
|
||||
}
|
||||
],
|
||||
"extension_attributes": {
|
||||
"shipping_assignments": [
|
||||
{
|
||||
"shipping": {
|
||||
"total": {
|
||||
"base_shipping_amount": 0,
|
||||
"base_shipping_discount_amount": 0,
|
||||
"base_shipping_incl_tax": 0,
|
||||
"base_shipping_tax_amount": 0,
|
||||
"shipping_amount": 0,
|
||||
"shipping_discount_amount": 0,
|
||||
"shipping_discount_tax_compensation_amount": 0,
|
||||
"shipping_incl_tax": 0,
|
||||
"shipping_tax_amount": 0
|
||||
}
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"amount_refunded": 0,
|
||||
"base_amount_refunded": 0,
|
||||
"base_discount_amount": 0,
|
||||
"base_discount_invoiced": 0,
|
||||
"base_discount_tax_compensation_amount": 0,
|
||||
"base_original_price": 24,
|
||||
"base_price": 23.41,
|
||||
"base_price_incl_tax": 24,
|
||||
"base_row_invoiced": 0,
|
||||
"base_row_total": 23.41,
|
||||
"base_row_total_incl_tax": 24,
|
||||
"base_tax_amount": 0.59,
|
||||
"base_tax_invoiced": 0,
|
||||
"created_at": "2018-07-19 15:19:00",
|
||||
"discount_amount": 0,
|
||||
"discount_invoiced": 0,
|
||||
"discount_percent": 0,
|
||||
"free_shipping": 0,
|
||||
"discount_tax_compensation_amount": 0,
|
||||
"is_qty_decimal": 0,
|
||||
"is_virtual": 1,
|
||||
"item_id": 80320,
|
||||
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||
"no_discount": 0,
|
||||
"order_id": 57614,
|
||||
"original_price": 24,
|
||||
"price": 23.41,
|
||||
"price_incl_tax": 24,
|
||||
"product_id": 8654,
|
||||
"product_type": "virtual",
|
||||
"qty_canceled": 0,
|
||||
"qty_invoiced": 0,
|
||||
"qty_ordered": 1,
|
||||
"qty_refunded": 0,
|
||||
"qty_shipped": 0,
|
||||
"quote_item_id": 135169,
|
||||
"row_invoiced": 0,
|
||||
"row_total": 23.41,
|
||||
"row_total_incl_tax": 24,
|
||||
"sku": "978-3-0355-1185-7",
|
||||
"store_id": 1,
|
||||
"tax_amount": 0.59,
|
||||
"tax_invoiced": 0,
|
||||
"tax_percent": 2.5,
|
||||
"updated_at": "2018-07-19 15:19:00",
|
||||
"weight": 0.01
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"search_criteria": {
|
||||
"filter_groups": [
|
||||
{
|
||||
"filters": [
|
||||
{
|
||||
"field": "customer_id",
|
||||
"value": "49124",
|
||||
"condition_type": "eq"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"total_count": 2
|
||||
}
|
||||
|
|
@ -16,11 +16,10 @@ class ApiAccessTestCase(TestCase):
|
|||
def test_graphqlEndpoint_shouldNotBeAccessibleWithoutLogin(self):
|
||||
c = Client()
|
||||
response = c.post('/api/graphql/', data=self.query, content_type='application/json')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, '/login?next=/api/graphql/')
|
||||
self.assertEqual(response.status_code, 402)
|
||||
|
||||
def test_graphqlEndpoint_shouldBeAccessibleWithLogin(self):
|
||||
UserFactory(username='admin')
|
||||
def test_graphqlEndpoint_shouldBeAccessibleForSuperUser(self):
|
||||
UserFactory(username='admin', is_staff=True, is_active=True, is_superuser=True)
|
||||
|
||||
c = Client()
|
||||
c.login(username='admin', password='test')
|
||||
|
|
|
|||
|
|
@ -1,89 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 03.02.20
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.test import TestCase, RequestFactory
|
||||
from graphene.test import Client
|
||||
|
||||
from api.schema import schema
|
||||
from core.factories import UserFactory
|
||||
from core.hep_client import HepClient
|
||||
from core.tests.mock_hep_data_factory import MockResponse, VALID_TEACHERS_ORDERS
|
||||
from users.models import License, Role, SchoolClass, UserRole
|
||||
|
||||
|
||||
class CouponTests(TestCase):
|
||||
def setUp(self):
|
||||
Role.objects.create_default_roles()
|
||||
|
||||
self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch', hep_id=3)
|
||||
Role.objects.create_default_roles()
|
||||
self.teacher_role = Role.objects.get_default_teacher_role()
|
||||
|
||||
# adding session
|
||||
request = RequestFactory().post('/')
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.user = self.user
|
||||
request.session.save()
|
||||
self.client = Client(schema=schema, context_value=request)
|
||||
|
||||
def make_coupon_mutation(self, coupon_code, client):
|
||||
mutation = '''
|
||||
mutation Coupon($input: CouponInput!){
|
||||
coupon(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
return client.execute(mutation, variables={
|
||||
'input': {
|
||||
'couponCode': coupon_code
|
||||
}
|
||||
})
|
||||
|
||||
@patch.object(requests, 'put', return_value=MockResponse(200, data=['200', 'Coupon successfully redeemed']))
|
||||
@patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS)
|
||||
@patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
|
||||
def test_user_has_valid_coupon(self, admin_mock, orders_mock, response_mock):
|
||||
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||
|
||||
user_role_key = self.user.user_roles.get(user=self.user).role.key
|
||||
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
|
||||
license = License.objects.get(licensee=self.user)
|
||||
self.assertIsNotNone(license)
|
||||
|
||||
school_class = SchoolClass.objects.get(users__in=[self.user])
|
||||
self.assertIsNotNone(school_class)
|
||||
|
||||
self.assertTrue(result.get('data').get('coupon').get('success'))
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
@patch.object(requests, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon']))
|
||||
def test_user_has_invalid_coupon(self, response_mock):
|
||||
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||
|
||||
self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon')
|
||||
|
||||
@patch.object(requests, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon']))
|
||||
def test_unauthenticated_user_cannot_redeem(self, response_mock):
|
||||
|
||||
request = RequestFactory().post('/')
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
client = Client(schema=schema, context_value=request)
|
||||
|
||||
result = self.make_coupon_mutation('COUPON--1234', client)
|
||||
|
||||
self.assertEqual(result.get('errors')[0].get('message'), 'not_authenticated')
|
||||
|
|
@ -24,7 +24,8 @@ urlpatterns = [
|
|||
url(r'^api/', include('api.urls', namespace="api")),
|
||||
|
||||
#favicon
|
||||
url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon@2x.png', permanent=True))
|
||||
url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon@2x.png', permanent=True)),
|
||||
|
||||
]
|
||||
|
||||
if settings.DEBUG and not settings.USE_AWS:
|
||||
|
|
|
|||
|
|
@ -28,17 +28,15 @@ def is_private_api_call_allowed(user, body):
|
|||
# logged in users should only be able to access all resources if they have a valid license
|
||||
# logged in users without valid license have only access to logout, me & coupon mutations
|
||||
|
||||
body_unicode = body.decode('utf-8')
|
||||
if user.is_anonymous:
|
||||
return False
|
||||
|
||||
try:
|
||||
if not user.hep_id:
|
||||
return True
|
||||
except AttributeError:
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
# logout, me and coupon resources are always allowed. Even if the user has no valid license
|
||||
if re.search(r"mutation\s*.*\s*logout\s*{", body_unicode) or re.search(r"query\s*.*\s*me\s*{", body_unicode) \
|
||||
or re.search(r"mutation\s*Coupon", body_unicode):
|
||||
body_unicode = body.decode('utf-8')
|
||||
|
||||
if is_endpoint_allowed(body_unicode):
|
||||
return True
|
||||
|
||||
license_expiry = user.license_expiry_date
|
||||
|
|
@ -50,6 +48,12 @@ def is_private_api_call_allowed(user, body):
|
|||
return True
|
||||
|
||||
|
||||
# logout, betalogin, me and coupon resources are always allowed. Even if the user has no valid license
|
||||
def is_endpoint_allowed(body):
|
||||
return re.search(r"mutation\s*.*\s*logout\s*{", body) or re.search(r"query\s*.*\s*me\s*{", body) \
|
||||
or re.search(r"mutation\s*Coupon", body) or re.search(r"mutation\s*BetaLogin", body)
|
||||
|
||||
|
||||
def sync_hidden_for(model, school_class_template, school_class_to_sync):
|
||||
if model.hidden_for.filter(id=school_class_template.id).exists() and not model.hidden_for.filter(
|
||||
id=school_class_to_sync.id).exists():
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@ from django.views.decorators.csrf import ensure_csrf_cookie
|
|||
from django.views.generic import TemplateView
|
||||
from graphene_django.views import GraphQLView
|
||||
|
||||
from core.hep_client import HepClient
|
||||
from core.models import AdminData
|
||||
|
||||
|
||||
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
|
||||
pass
|
||||
|
|
@ -27,20 +24,3 @@ def home(request):
|
|||
print('Can not connect to dev server at http://localhost:8080:', e)
|
||||
|
||||
return render(request, 'index.html', {})
|
||||
|
||||
|
||||
class ConfirmationKeyDisplayView(TemplateView):
|
||||
template_name = 'confirmation_key.html'
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
|
||||
email = self.request.GET.get('email', '')
|
||||
|
||||
hep_client = HepClient()
|
||||
admin_token = AdminData.objects.get_admin_token()
|
||||
hep_user = hep_client.customers_search(admin_token, email)
|
||||
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['confirmation_key'] = hep_user['confirmation']
|
||||
context['hep_id'] = hep_user['id']
|
||||
return context
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UserConfig(AppConfig):
|
||||
name = 'oauth'
|
||||
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
import factory
|
||||
from django.utils import timezone
|
||||
|
||||
from oauth.models import OAuth2Token
|
||||
|
||||
IN_A_HOUR = timezone.now() + timedelta(hours=1)
|
||||
IN_A_HOUR_UNIX = int(time.mktime(IN_A_HOUR.timetuple()))
|
||||
|
||||
|
||||
class Oauth2TokenFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = OAuth2Token
|
||||
|
||||
token_type = 'Bearer'
|
||||
access_token = 'asdfgghh'
|
||||
refresh_token = 'yxcvbnnm'
|
||||
expires_at = IN_A_HOUR_UNIX
|
||||
|
||||
|
|
@ -0,0 +1,193 @@
|
|||
import requests
|
||||
from django.conf import settings
|
||||
from django.utils.dateparse import parse_datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from oauth.models import OAuth2Token
|
||||
from oauth.oauth_client import oauth, fetch_token
|
||||
from users.licenses import MYSKILLBOX_LICENSES, is_myskillbox_product, TEACHER_KEY
|
||||
from users.models import License
|
||||
|
||||
from core.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
VALID_PRODUCT_STATES = ['waiting', 'paid', 'completed', 'shipped']
|
||||
|
||||
|
||||
class HepClientException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HepClientUnauthorizedException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HepClientNoTokenException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class HepClient:
|
||||
|
||||
def _call(self, url, token_dict, method='get', data=None):
|
||||
|
||||
headers = {"accept": "application/json"}
|
||||
|
||||
if method == 'post':
|
||||
response = oauth.hep.post(url, json=data, token=token_dict, headers=headers)
|
||||
elif method == 'get':
|
||||
response = oauth.hep.get(url, params=data, token=token_dict, headers=headers)
|
||||
elif method == 'put':
|
||||
response = oauth.hep.put(url, data=data, token=token_dict, headers=headers)
|
||||
|
||||
return self._handle_response(response)
|
||||
|
||||
def _handle_response(self, response):
|
||||
if response.status_code == 401:
|
||||
raise HepClientUnauthorizedException(response.status_code, response.json())
|
||||
elif response.status_code != 200:
|
||||
logger.warning(f'Hepclient error: Received {response.status_code} {response.json()}')
|
||||
raise HepClientException(response.status_code, response.json())
|
||||
|
||||
return response
|
||||
|
||||
def _get_valid_token(self, request, token_dict):
|
||||
if request is None and token_dict is None:
|
||||
raise HepClientNoTokenException
|
||||
|
||||
if not token_dict:
|
||||
token_dict = fetch_token('', request)
|
||||
if not token_dict:
|
||||
raise HepClientNoTokenException
|
||||
|
||||
if OAuth2Token.is_dict_expired(token_dict):
|
||||
refresh_data = self._refresh_token(token_dict)
|
||||
token, refresh_success = OAuth2Token.update_dict_with_refresh_data(refresh_data, token_dict['access_token'])
|
||||
if not refresh_success:
|
||||
raise HepClientUnauthorizedException
|
||||
|
||||
token_dict = token.to_token()
|
||||
|
||||
return token_dict
|
||||
|
||||
def _refresh_token(self, token_dict):
|
||||
|
||||
data = {
|
||||
'grant_type': 'refresh_token',
|
||||
'refresh_token': token_dict['refresh_token'],
|
||||
'client_id': settings.AUTHLIB_OAUTH_CLIENTS['hep']['client_id'],
|
||||
'client_secret': settings.AUTHLIB_OAUTH_CLIENTS['hep']['client_secret'],
|
||||
'scope': ''
|
||||
}
|
||||
|
||||
response = requests.post(f'{settings.AUTHLIB_OAUTH_CLIENTS["hep"]["api_base_url"]}/oauth/token', json=data)
|
||||
return self._handle_response(response).json()
|
||||
|
||||
def is_email_verified(self, user_data):
|
||||
return user_data['email_verified_at'] is not None
|
||||
|
||||
def user_details(self, request=None, token_dict=None):
|
||||
token_dict = self._get_valid_token(request, token_dict)
|
||||
response = self._call('api/auth/user', token_dict)
|
||||
return response.json()['data']
|
||||
|
||||
def logout(self, request=None, token_dict=None):
|
||||
token_dict = self._get_valid_token(request, token_dict)
|
||||
self._call('api/auth/logout', token_dict, method='post')
|
||||
return True
|
||||
|
||||
def fetch_eorders(self, request=None, token_dict=None):
|
||||
token_dict = self._get_valid_token(request, token_dict)
|
||||
data = {
|
||||
'filters[product_type]': 'eLehrmittel',
|
||||
}
|
||||
response = self._call('api/partners/users/orders/search', token_dict, data=data)
|
||||
return response.json()['data']
|
||||
|
||||
def active_myskillbox_product_for_customer(self, request=None, token_dict=None):
|
||||
eorders = self.fetch_eorders(request=request, token_dict=token_dict)
|
||||
|
||||
myskillbox_products = self._extract_myskillbox_products(eorders)
|
||||
|
||||
if len(myskillbox_products) == 0:
|
||||
return None
|
||||
else:
|
||||
return self._get_active_product(myskillbox_products)
|
||||
|
||||
def redeem_coupon(self, coupon_code, customer_id, request=None, token_dict=None):
|
||||
token_dict = self._get_valid_token(request, token_dict)
|
||||
try:
|
||||
response = self._call(f'api/partners/users/{customer_id}/coupons/redeem', token_dict, method='post',
|
||||
data={'code': coupon_code})
|
||||
except HepClientException:
|
||||
return None
|
||||
|
||||
response_data = response.json()
|
||||
return response_data
|
||||
|
||||
def _extract_myskillbox_products(self, eorders):
|
||||
products = []
|
||||
|
||||
for eorder in eorders:
|
||||
|
||||
if 'items' not in eorder:
|
||||
continue
|
||||
|
||||
status = eorder.get('status', '')
|
||||
|
||||
for entry in eorder['items']:
|
||||
product = self.entry_to_product(entry, self._get_item_activation(eorder), status)
|
||||
|
||||
if product:
|
||||
products.append(product)
|
||||
|
||||
return products
|
||||
|
||||
def entry_to_product(self, entry, activation_date, status):
|
||||
if is_myskillbox_product(entry['isbn']) and activation_date:
|
||||
return {
|
||||
'raw': entry,
|
||||
'activated': activation_date,
|
||||
'status': status,
|
||||
'order_id': entry['id'],
|
||||
'license': MYSKILLBOX_LICENSES[entry['isbn']],
|
||||
'isbn': entry['isbn']
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
def _get_item_activation(self, eorder):
|
||||
if 'created_at' in eorder:
|
||||
return parse_datetime(eorder['created_at'])
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_active_product(self, products):
|
||||
|
||||
def filter_valid_products(product):
|
||||
|
||||
if product['status'] not in VALID_PRODUCT_STATES:
|
||||
return False
|
||||
|
||||
expiry_delta = product['activated'] + timedelta(product['license']['duration'])
|
||||
|
||||
return License.is_product_active(expiry_delta, product['isbn'])
|
||||
|
||||
active_products = list(filter(filter_valid_products, products))
|
||||
|
||||
if len(active_products) == 0:
|
||||
return None
|
||||
elif len(active_products) == 1:
|
||||
return active_products[0]
|
||||
else:
|
||||
return self._select_from_teacher_products(active_products)
|
||||
|
||||
def _select_from_teacher_products(self, active_products):
|
||||
|
||||
# select first teacher product, as they are all valid it does not matter which one
|
||||
for product in active_products:
|
||||
if product['license']['edition'] == TEACHER_KEY:
|
||||
return product
|
||||
|
||||
# select a student product, as they are all valid it does not matter which one
|
||||
return active_products[0]
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class OAuth2TokenManager(models.Manager):
|
||||
def update_or_create_token(self, token_data, user):
|
||||
|
||||
query = self.filter(user=user)
|
||||
token_properties = {
|
||||
'token_type': token_data['token_type'],
|
||||
'access_token': token_data['access_token'],
|
||||
'refresh_token': token_data['refresh_token'],
|
||||
'expires_at': token_data['expires_at'],
|
||||
}
|
||||
|
||||
if query.exists():
|
||||
return query.update(**token_properties)
|
||||
else:
|
||||
return self._create_oauthtoken(user, token_properties)
|
||||
|
||||
def _create_oauthtoken(self, user, token_properties):
|
||||
token = self.create(user=user, **token_properties)
|
||||
return token
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import json
|
||||
|
||||
from django.http import HttpResponse
|
||||
from core.utils import is_private_api_call_allowed
|
||||
|
||||
|
||||
def user_has_license_middleware(get_response):
|
||||
|
||||
def middleware(request):
|
||||
if request.path == '/api/graphql/':
|
||||
if not is_private_api_call_allowed(request.user, request.body):
|
||||
return HttpResponse(json.dumps({'errors': ['no active license']}), status=402)
|
||||
|
||||
return get_response(request)
|
||||
|
||||
return middleware
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Generated by Django 2.2.21 on 2021-05-12 14:06
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OAuth2Token',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('token_type', models.CharField(max_length=40)),
|
||||
('access_token', models.TextField()),
|
||||
('refresh_token', models.TextField()),
|
||||
('expires_at', models.PositiveIntegerField()),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
# https://docs.authlib.org/en/latest/client/frameworks.html#frameworks-clients
|
||||
import base64
|
||||
import json
|
||||
from time import mktime
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
from oauth.managers import OAuth2TokenManager
|
||||
|
||||
from core.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class OAuth2Token(models.Model):
|
||||
token_type = models.CharField(max_length=40)
|
||||
access_token = models.TextField()
|
||||
refresh_token = models.TextField()
|
||||
expires_at = models.PositiveIntegerField()
|
||||
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
|
||||
|
||||
objects = OAuth2TokenManager()
|
||||
|
||||
@classmethod
|
||||
def is_dict_expired(cls, token_dict):
|
||||
return cls.has_timestamp_expired(token_dict['expires_at'])
|
||||
|
||||
@classmethod
|
||||
def has_timestamp_expired(cls, expires_at):
|
||||
now = timezone.now()
|
||||
now_unix_timestamp = int(mktime(now.timetuple()))
|
||||
|
||||
return expires_at < now_unix_timestamp
|
||||
|
||||
@classmethod
|
||||
def update_dict_with_refresh_data(cls, data, old_access_token):
|
||||
try:
|
||||
token = cls.objects.get(access_token=old_access_token)
|
||||
except cls.DoesNotExist:
|
||||
return False
|
||||
|
||||
return token.update_with_refresh_data(data)
|
||||
|
||||
def to_token(self):
|
||||
return dict(
|
||||
access_token=self.access_token,
|
||||
token_type=self.token_type,
|
||||
refresh_token=self.refresh_token,
|
||||
expires_at=self.expires_at,
|
||||
)
|
||||
|
||||
def has_expired(self):
|
||||
return OAuth2Token.has_timestamp_expired(self.expires_at)
|
||||
|
||||
def update_with_refresh_data(self, data):
|
||||
payload = self._jwt_payload(data['access_token'])
|
||||
if not payload:
|
||||
return None, False
|
||||
|
||||
self.token_type = data['token_type']
|
||||
self.access_token = data['access_token']
|
||||
self.refresh_token = data['refresh_token']
|
||||
|
||||
self.expires_at = int(payload['exp'])
|
||||
self.save()
|
||||
|
||||
return self, True
|
||||
|
||||
def _jwt_payload(self, jwt):
|
||||
jwt_parts = jwt.split('.')
|
||||
try:
|
||||
payload_bytes = base64.b64decode(jwt_parts[1])
|
||||
payload = json.loads(payload_bytes.decode("UTF-8"))
|
||||
except Exception as e:
|
||||
logger.warning(f'OAuthToken error: Could not decode jwt: {e}')
|
||||
return None
|
||||
return payload
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import graphene
|
||||
from django.contrib.auth import logout
|
||||
from django.utils.timezone import now
|
||||
from graphene import relay
|
||||
|
||||
from oauth.hep_client import HepClient, HepClientException
|
||||
from oauth.models import OAuth2Token
|
||||
from oauth.user_signup_login_handler import create_role_for_user
|
||||
from users.models import License
|
||||
|
||||
|
||||
class Coupon(relay.ClientIDMutation):
|
||||
class Input:
|
||||
coupon_code = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
coupon_code = kwargs.get('coupon_code').strip()
|
||||
hep_client = HepClient()
|
||||
|
||||
try:
|
||||
hep_id = info.context.user.hep_id
|
||||
except AttributeError:
|
||||
raise Exception('not_authenticated')
|
||||
|
||||
try:
|
||||
response = hep_client.redeem_coupon(coupon_code, hep_id, request=info.context)
|
||||
except HepClientException:
|
||||
raise Exception('unknown_error')
|
||||
|
||||
if not response:
|
||||
raise Exception('invalid_coupon')
|
||||
|
||||
product = hep_client.entry_to_product(response['data'], now(), 'coupon')
|
||||
|
||||
if not product:
|
||||
raise Exception('non_myskillbox_product')
|
||||
|
||||
license = License.objects.create_license_for_role(info.context.user, product['activated'], product['raw'],
|
||||
product['license']['edition'],
|
||||
product['order_id'], product['isbn'])
|
||||
|
||||
create_role_for_user(info.context.user, license.for_role.key)
|
||||
|
||||
return cls(success=True)
|
||||
|
||||
|
||||
class CouponMutations:
|
||||
redeem_coupon = Coupon.Field()
|
||||
|
||||
|
||||
class Logout(graphene.Mutation):
|
||||
success = graphene.Boolean()
|
||||
|
||||
def mutate(self, info):
|
||||
# log user out from myskillbox even if hep logout fails
|
||||
try:
|
||||
token = OAuth2Token.objects.get(user=info.context.user)
|
||||
hep_client = HepClient()
|
||||
try:
|
||||
hep_client.logout(request=info.context)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
token.delete()
|
||||
|
||||
except OAuth2Token.DoesNotExist:
|
||||
pass
|
||||
|
||||
logout(info.context)
|
||||
return Logout(success=True)
|
||||
|
||||
|
||||
class OauthMutations(object):
|
||||
logout = Logout.Field()
|
||||
coupon = Coupon.Field()
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
from authlib.integrations.django_client import OAuth
|
||||
from django.conf import settings
|
||||
|
||||
from oauth.models import OAuth2Token
|
||||
|
||||
|
||||
# https://docs.authlib.org/en/latest/client/frameworks.html#frameworks-clients
|
||||
def fetch_token(name, request):
|
||||
try:
|
||||
token = OAuth2Token.objects.get(
|
||||
user=request.user
|
||||
)
|
||||
return token.to_token()
|
||||
except (OAuth2Token.DoesNotExist, TypeError):
|
||||
return None
|
||||
|
||||
|
||||
oauth = OAuth(fetch_token=fetch_token)
|
||||
oauth.register(
|
||||
name='hep',
|
||||
client_id=settings.AUTHLIB_OAUTH_CLIENTS['hep']['client_id'],
|
||||
client_secret=settings.AUTHLIB_OAUTH_CLIENTS['hep']['client_secret'],
|
||||
request_token_url=settings.AUTHLIB_OAUTH_CLIENTS['hep']['request_token_url'],
|
||||
request_token_params=None,
|
||||
access_token_url=settings.AUTHLIB_OAUTH_CLIENTS['hep']['access_token_url'],
|
||||
access_token_params=None,
|
||||
authorize_url=settings.AUTHLIB_OAUTH_CLIENTS['hep']['authorize_url'],
|
||||
authorize_params=None,
|
||||
api_base_url=settings.AUTHLIB_OAUTH_CLIENTS['hep']['api_base_url'],
|
||||
client_kwargs=settings.AUTHLIB_OAUTH_CLIENTS['hep']['client_kwargs'],
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
[
|
||||
{
|
||||
"id": 5,
|
||||
"status": "paid",
|
||||
"order_total": 10000,
|
||||
"created_at": "2021-05-10T20:52:16.000000Z",
|
||||
"billing_address": {
|
||||
"id": 20,
|
||||
"salutation": "male",
|
||||
"first_name": "Hans",
|
||||
"last_name": "Meier",
|
||||
"company": "Lustig GmbH",
|
||||
"street": "Sulgenrain 12890",
|
||||
"city": "Bern",
|
||||
"post_code": "3007",
|
||||
"country": "Schweiz"
|
||||
},
|
||||
"delivery_address": {
|
||||
"id": 19,
|
||||
"salutation": "male",
|
||||
"first_name": "Hans",
|
||||
"last_name": "Muster",
|
||||
"company": "Muster AG",
|
||||
"street": "Ruderweg 24",
|
||||
"city": "Bern",
|
||||
"post_code": "3000",
|
||||
"country": "Schweiz"
|
||||
},
|
||||
"entries": [
|
||||
{
|
||||
"id": 3433,
|
||||
"uri": "\/products\/myskillbox-lernende",
|
||||
"url": null,
|
||||
"title": "mySkillbox für Lernende ",
|
||||
"subtitle": "Lizenz gültig für 4 Jahre",
|
||||
"isbn": "978-3-0355-1397-4",
|
||||
"slug": "myskillbox-lernende",
|
||||
"product_type": "eLehrmittel",
|
||||
"product_form": "",
|
||||
"cover": "https:\/\/hep-verlag.fra1.digitaloceanspaces.com\/staging\/products\/2921\/978-3-0355-1397-4.jpg",
|
||||
"price": 100,
|
||||
"price_total": 100,
|
||||
"amount": 1,
|
||||
"authors": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
from unittest.mock import patch
|
||||
|
||||
from authlib.integrations.base_client import BaseApp
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.test import TestCase, RequestFactory
|
||||
from graphene.test import Client
|
||||
|
||||
from api.schema import schema
|
||||
from core.factories import UserFactory
|
||||
from oauth.factories import Oauth2TokenFactory
|
||||
from users.tests.mock_hep_data_factory import MockResponse
|
||||
from users.models import License, Role, SchoolClass
|
||||
|
||||
REDEEM_MYSKILLBOX_SUCCESS_RESPONSE = {
|
||||
"data": {
|
||||
"id": 3433,
|
||||
"uri": "\/products\/myskillbox-lehrpersonen",
|
||||
"url": None,
|
||||
"title": "mySkillbox für Lehrpersonen ",
|
||||
"subtitle": "Lizenz gültig für 1 Jahr",
|
||||
"isbn": "978-3-0355-1861-0",
|
||||
"slug": "myskillbox-lehrpersonen",
|
||||
"product_type": "eLehrmittel",
|
||||
"product_form": "",
|
||||
"cover": "https:\/\/hep-verlag.fra1.digitaloceanspaces.com\/staging\/products\/2921\/978-3-0355-1861-0.jpg",
|
||||
"price": 100,
|
||||
"price_total": 100,
|
||||
"amount": 1,
|
||||
"authors": []
|
||||
}
|
||||
}
|
||||
|
||||
REDEEM_OTHER_LICENSE_RESPONSE = {
|
||||
"data": {
|
||||
"id": 3433,
|
||||
"uri": "\/products\/someothe",
|
||||
"url": None,
|
||||
"title": "Ein e-Lehrmittel",
|
||||
"subtitle": "Lizenz gültig für 1 Jahr",
|
||||
"isbn": "111-2-3333-4444-0",
|
||||
"slug": "some-other",
|
||||
"product_type": "eLehrmittel",
|
||||
"product_form": "",
|
||||
"cover": "https:\/\/hep-verlag.fra1.digitaloceanspaces.com\/staging\/products\/2921\/978-3-0355-123.jpg",
|
||||
"price": 100,
|
||||
"price_total": 100,
|
||||
"amount": 1,
|
||||
"authors": []
|
||||
}
|
||||
}
|
||||
|
||||
INVALID_LICENSE = {
|
||||
"message": "The given data was invalid.",
|
||||
"errors": {
|
||||
"code": [
|
||||
"The coupons was already redeemed."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CouponTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch', hep_id=3)
|
||||
Role.objects.create_default_roles()
|
||||
self.teacher_role = Role.objects.get_default_teacher_role()
|
||||
Oauth2TokenFactory(user=self.user)
|
||||
|
||||
# adding session
|
||||
request = RequestFactory().post('/')
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.user = self.user
|
||||
request.session.save()
|
||||
self.client = Client(schema=schema, context_value=request)
|
||||
|
||||
def make_coupon_mutation(self, coupon_code, client):
|
||||
mutation = '''
|
||||
mutation Coupon($input: CouponInput!){
|
||||
coupon(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
return client.execute(mutation, variables={
|
||||
'input': {
|
||||
'couponCode': coupon_code
|
||||
}
|
||||
})
|
||||
|
||||
@patch.object(BaseApp, 'post', return_value=MockResponse(200, data=REDEEM_MYSKILLBOX_SUCCESS_RESPONSE))
|
||||
def test_user_has_valid_skillbox_coupon(self, response_mock):
|
||||
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||
|
||||
user_role = self.user.user_roles.get(user=self.user)
|
||||
self.assertEqual(user_role.role.key, Role.objects.TEACHER_KEY)
|
||||
license = License.objects.get(licensee=self.user)
|
||||
self.assertIsNotNone(license)
|
||||
|
||||
school_class = SchoolClass.objects.get(users__in=[self.user])
|
||||
self.assertIsNotNone(school_class)
|
||||
|
||||
self.assertTrue(result.get('data').get('coupon').get('success'))
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
@patch.object(BaseApp, 'post', return_value=MockResponse(200, data=REDEEM_OTHER_LICENSE_RESPONSE))
|
||||
def test_user_has_valid_non_skillbox_coupon(self, response_mock):
|
||||
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||
|
||||
try:
|
||||
self.user.user_roles.get(user=self.user)
|
||||
self.fail("CouponTests.test_user_has_valid_non_skillbox_coupon: Should not have created user role")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
License.objects.get(licensee=self.user)
|
||||
self.fail("CouponTests.test_user_has_valid_non_skillbox_coupon: License should not exist")
|
||||
except License.DoesNotExist:
|
||||
pass
|
||||
|
||||
self.assertEqual(result.get('errors')[0].get('message'), 'non_myskillbox_product')
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
@patch.object(BaseApp, 'post', return_value=MockResponse(404, data=INVALID_LICENSE))
|
||||
def test_user_has_invalid_coupon(self, response_mock):
|
||||
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||
|
||||
self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon')
|
||||
|
||||
@patch.object(BaseApp, 'post', return_value=MockResponse(422, data=INVALID_LICENSE))
|
||||
def test_user_has_already_used_coupon(self, response_mock):
|
||||
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||
|
||||
self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon')
|
||||
|
||||
@patch.object(BaseApp, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon']))
|
||||
def test_unauthenticated_user_cannot_redeem(self, response_mock):
|
||||
request = RequestFactory().post('/')
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
client = Client(schema=schema, context_value=request)
|
||||
|
||||
result = self.make_coupon_mutation('COUPON--1234', client)
|
||||
|
||||
self.assertEqual(result.get('errors')[0].get('message'), 'not_authenticated')
|
||||
|
|
@ -1,8 +1,17 @@
|
|||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
from django.test import TestCase
|
||||
from core.hep_client import HepClient, MYSKILLBOX_LICENSES
|
||||
|
||||
from core.factories import UserFactory
|
||||
from oauth.factories import Oauth2TokenFactory
|
||||
from oauth.hep_client import HepClient, HepClientUnauthorizedException, HepClientNoTokenException, HepClientException
|
||||
from oauth.models import OAuth2Token
|
||||
from oauth.tests.test_oauth2token import REFRESH_DATA
|
||||
from users.licenses import MYSKILLBOX_LICENSES
|
||||
from users.models import License
|
||||
from users.tests.mock_hep_data_factory import MockResponse
|
||||
|
||||
ISBNS = list(MYSKILLBOX_LICENSES.keys())
|
||||
|
||||
|
|
@ -18,6 +27,12 @@ class HepClientTestCases(TestCase):
|
|||
self.hep_client = HepClient()
|
||||
self.now = datetime.now()
|
||||
|
||||
def _create_token(self):
|
||||
user = UserFactory(username="bert")
|
||||
token = Oauth2TokenFactory(user=user)
|
||||
self.token_dict = token.to_token()
|
||||
return self.token_dict
|
||||
|
||||
def test_has_no_valid_product(self):
|
||||
products = [
|
||||
{
|
||||
|
|
@ -25,25 +40,25 @@ class HepClientTestCases(TestCase):
|
|||
'isbn': TEACHER_ISBN,
|
||||
'raw': {},
|
||||
'activated': self.now - timedelta(2*TEACHER_LICENSE['duration']),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
},
|
||||
{
|
||||
'license': TEACHER_LICENSE,
|
||||
'isbn': TEACHER_ISBN,
|
||||
'raw': {},
|
||||
'activated': self.now - timedelta(3 * TEACHER_LICENSE['duration']),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
},
|
||||
{
|
||||
'license': TEACHER_LICENSE,
|
||||
'isbn': TEACHER_ISBN,
|
||||
'raw': {},
|
||||
'activated': self.now - timedelta(4 * TEACHER_LICENSE['duration']),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
}
|
||||
]
|
||||
|
||||
relevant_product = self.hep_client._get_relevant_product(products)
|
||||
relevant_product = self.hep_client._get_active_product(products)
|
||||
self.assertIsNone(relevant_product)
|
||||
|
||||
def test_has_no_not_completed_product(self):
|
||||
|
|
@ -57,7 +72,7 @@ class HepClientTestCases(TestCase):
|
|||
}
|
||||
]
|
||||
|
||||
relevant_product = self.hep_client._get_relevant_product(products)
|
||||
relevant_product = self.hep_client._get_active_product(products)
|
||||
self.assertIsNone(relevant_product)
|
||||
|
||||
def test_has_valid_product(self):
|
||||
|
|
@ -69,7 +84,7 @@ class HepClientTestCases(TestCase):
|
|||
'id': 0
|
||||
},
|
||||
'activated': self.now - timedelta(7),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
},
|
||||
{
|
||||
'license': TEACHER_LICENSE,
|
||||
|
|
@ -78,7 +93,7 @@ class HepClientTestCases(TestCase):
|
|||
'id': 1
|
||||
},
|
||||
'activated': self.now - timedelta(3 * TEACHER_LICENSE['duration']),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
},
|
||||
{
|
||||
'license': TEACHER_LICENSE,
|
||||
|
|
@ -87,11 +102,11 @@ class HepClientTestCases(TestCase):
|
|||
'id': 2
|
||||
},
|
||||
'activated': self.now - timedelta(4 * TEACHER_LICENSE['duration']),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
}
|
||||
]
|
||||
|
||||
relevant_product = self.hep_client._get_relevant_product(products)
|
||||
relevant_product = self.hep_client._get_active_product(products)
|
||||
self.assertEqual(relevant_product['raw']['id'], 0)
|
||||
|
||||
def test_has_multiple_valid_teacher_products_but_only_one_active(self):
|
||||
|
|
@ -103,7 +118,7 @@ class HepClientTestCases(TestCase):
|
|||
'id': 0
|
||||
},
|
||||
'activated': self.now - timedelta(7),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
},
|
||||
{
|
||||
'license': TEACHER_LICENSE,
|
||||
|
|
@ -112,7 +127,7 @@ class HepClientTestCases(TestCase):
|
|||
'id': 1
|
||||
},
|
||||
'activated': self.now - timedelta(3 * TEACHER_LICENSE['duration']),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
},
|
||||
{
|
||||
'license': TEACHER_LICENSE,
|
||||
|
|
@ -121,11 +136,11 @@ class HepClientTestCases(TestCase):
|
|||
'id': 2
|
||||
},
|
||||
'activated': self.now - timedelta(4 * TEACHER_LICENSE['duration']),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
}
|
||||
]
|
||||
|
||||
relevant_product = self.hep_client._get_relevant_product(products)
|
||||
relevant_product = self.hep_client._get_active_product(products)
|
||||
self.assertEqual(relevant_product['raw']['id'], 0)
|
||||
|
||||
def test_has_valid_student_and_teacher_edition(self):
|
||||
|
|
@ -137,7 +152,7 @@ class HepClientTestCases(TestCase):
|
|||
'id': 0
|
||||
},
|
||||
'activated': self.now - timedelta(7),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
},
|
||||
{
|
||||
'license': TEACHER_LICENSE,
|
||||
|
|
@ -146,7 +161,7 @@ class HepClientTestCases(TestCase):
|
|||
'id': 1
|
||||
},
|
||||
'activated': self.now - timedelta(7),
|
||||
'status': 'complete'
|
||||
'status': 'paid'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -157,12 +172,58 @@ class HepClientTestCases(TestCase):
|
|||
|
||||
expiry_date = self.now + timedelta(3)
|
||||
|
||||
is_active = HepClient.is_product_active(expiry_date, TEACHER_ISBN)
|
||||
is_active = License.is_product_active(expiry_date, TEACHER_ISBN)
|
||||
self.assertTrue(is_active)
|
||||
|
||||
def test_product_is_not_active(self):
|
||||
|
||||
expiry_date = self.now - timedelta(3 * TEACHER_LICENSE['duration'])
|
||||
|
||||
is_active = HepClient.is_product_active(expiry_date, TEACHER_ISBN)
|
||||
is_active = License.is_product_active(expiry_date, TEACHER_ISBN)
|
||||
self.assertFalse(is_active)
|
||||
|
||||
def test_token_is_not_valid_when_token_and_request_empty(self):
|
||||
|
||||
try:
|
||||
self.hep_client._get_valid_token(None, None)
|
||||
except HepClientNoTokenException:
|
||||
return
|
||||
|
||||
self.fail("HepClientTestCases.test_token_is_not_valid_when_token_and_request_empty: Should throw HepClientUnauthorizedException")
|
||||
|
||||
@patch.object(OAuth2Token, 'is_dict_expired', return_value=True)
|
||||
@patch.object(requests, 'post', return_value=MockResponse(400, data={}))
|
||||
def test_token_is_expired_and_cannot_be_refreshed_from_api(self, mock_fn1, mock_fn2):
|
||||
user = UserFactory(username='housi')
|
||||
token = Oauth2TokenFactory(user=user).to_token()
|
||||
|
||||
try:
|
||||
self.hep_client._get_valid_token(None, token)
|
||||
except HepClientException:
|
||||
return
|
||||
|
||||
self.fail("HepClientTestCases.test_token_is_expired_and_cannot_be_refreshed_from_api: Should throw HepClientUnauthorizedException")
|
||||
|
||||
@patch.object(OAuth2Token, 'is_dict_expired', return_value=True)
|
||||
@patch.object(requests, 'post', return_value=MockResponse(200, data={}))
|
||||
@patch.object(OAuth2Token, 'update_dict_with_refresh_data', return_value=(None, False))
|
||||
def test_token_is_expired_and_cannot_be_refreshed(self, mock_fn1, mock_fn2, mock_fn3):
|
||||
user = UserFactory(username='housi')
|
||||
token = Oauth2TokenFactory(user=user).to_token()
|
||||
|
||||
try:
|
||||
self.hep_client._get_valid_token(None, token)
|
||||
except HepClientUnauthorizedException:
|
||||
return
|
||||
|
||||
self.fail("HepClientTestCases.test_token_is_expired_and_cannot_be_refreshed: Should throw HepClientUnauthorizedException")
|
||||
|
||||
@patch.object(OAuth2Token, 'is_dict_expired', return_value=True)
|
||||
@patch.object(requests, 'post', return_value=MockResponse(200, data=REFRESH_DATA))
|
||||
def test_can_refresh_token(self, mock_fn1, mock_fn2):
|
||||
user = UserFactory(username='housi')
|
||||
token = Oauth2TokenFactory(user=user).to_token()
|
||||
|
||||
token_dict = self.hep_client._get_valid_token(None, token)
|
||||
self.assertEqual(token_dict['access_token'], REFRESH_DATA['access_token'])
|
||||
self.assertEqual(token_dict['refresh_token'], REFRESH_DATA['refresh_token'])
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
import time
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
from authlib.integrations.django_client import DjangoRemoteApp
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.test import TestCase, RequestFactory
|
||||
from django.utils import timezone
|
||||
|
||||
from core.factories import UserFactory
|
||||
from oauth.hep_client import HepClient
|
||||
from oauth.user_signup_login_handler import EMAIL_NOT_VERIFIED, NO_VALID_LICENSE, UNKNOWN_ERROR
|
||||
from oauth.views import authorize, OAUTH_REDIRECT
|
||||
from users.tests.mock_hep_data_factory import MockResponse, ME_DATA, VALID_STUDENT_ORDERS, VALID_TEACHERS_ORDERS
|
||||
from users.factories import LicenseFactory
|
||||
from users.models import Role, User, SchoolClass, License, UserData
|
||||
|
||||
IN_A_HOUR = timezone.now() + timedelta(hours=1)
|
||||
IN_A_HOUR_UNIX = time.mktime(IN_A_HOUR.timetuple())
|
||||
|
||||
TOKEN = {
|
||||
'token_type': 'hep',
|
||||
'access_token': '123456',
|
||||
'refresh_token': 'abcdqwer',
|
||||
'expires_at': IN_A_HOUR_UNIX,
|
||||
}
|
||||
|
||||
NEW_ME_DATA = ME_DATA.copy()
|
||||
NEW_ME_DATA['email'] = 'stiller@has.ch'
|
||||
NEW_ME_DATA['id'] = 99
|
||||
|
||||
|
||||
class LoginTests(TestCase):
|
||||
def setUp(self):
|
||||
self.user = UserFactory(username=ME_DATA['id'], email=ME_DATA['id'])
|
||||
Role.objects.create_default_roles()
|
||||
self.teacher_role = Role.objects.get_default_teacher_role()
|
||||
|
||||
self.factory = RequestFactory()
|
||||
|
||||
def _login(self, url):
|
||||
request = self.factory.get(url)
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
request.user = AnonymousUser()
|
||||
|
||||
return authorize(request)
|
||||
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=ME_DATA)
|
||||
def test_user_data_is_synced_on_login(self, user_mock, authorize_mock):
|
||||
|
||||
old_mail = 'aschi@iterativ.ch'
|
||||
|
||||
self.user.hep_id = ME_DATA['id']
|
||||
self.user.email = old_mail
|
||||
self.user.username = old_mail
|
||||
self.user.save()
|
||||
|
||||
now = timezone.now()
|
||||
expiry_date = now + timedelta(365)
|
||||
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
|
||||
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
user = User.objects.get(hep_id=self.user.hep_id)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state=success')
|
||||
self.assertEqual(user.username, ME_DATA['email'])
|
||||
self.assertEqual(user.email, ME_DATA['email'])
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
@patch.object(HepClient, 'fetch_eorders', return_value=VALID_TEACHERS_ORDERS)
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=ME_DATA)
|
||||
def test_teacher_can_login_with_valid_license(self, user_mock, authorize_mock, orders_mock):
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
|
||||
user = User.objects.get(email=ME_DATA['email'])
|
||||
|
||||
user_role_key = user.user_roles.get(user=user).role.key
|
||||
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
|
||||
|
||||
license = License.objects.get(licensee=user)
|
||||
self.assertEqual(license.for_role.key, Role.objects.TEACHER_KEY)
|
||||
|
||||
school_class = SchoolClass.objects.get(users__in=[user])
|
||||
self.assertIsNotNone(school_class)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state=success')
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
try:
|
||||
UserData.objects.get(user=user)
|
||||
self.fail('LoginTests.test_teacher_can_login_with_valid_license: Userdata should not exist')
|
||||
except:
|
||||
pass
|
||||
|
||||
@patch.object(HepClient, 'fetch_eorders', return_value=VALID_STUDENT_ORDERS)
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=ME_DATA)
|
||||
def test_student_can_login_with_valid_license(self, user_mock, authorize_mock, orders_mock):
|
||||
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
user = User.objects.get(email=ME_DATA['email'])
|
||||
|
||||
user_role_key = user.user_roles.get(user=user).role.key
|
||||
self.assertEqual(user_role_key, Role.objects.STUDENT_KEY)
|
||||
|
||||
license = License.objects.get(licensee=user)
|
||||
self.assertEqual(license.for_role.key, Role.objects.STUDENT_KEY)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state=success')
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
@patch.object(HepClient, 'is_email_verified', return_value=False)
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=ME_DATA)
|
||||
def test_user_with_unconfirmed_email_cannot_login(self, user_mock, authorize_mock, verified_mock):
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
|
||||
User.objects.get(email=ME_DATA['email'])
|
||||
self.assertEqual(302, response.status_code)
|
||||
self.assertEqual(f'/{OAUTH_REDIRECT}?state={EMAIL_NOT_VERIFIED}', response.url)
|
||||
|
||||
@patch.object(HepClient, 'fetch_eorders', return_value=[])
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=ME_DATA)
|
||||
def test_user_can_login_without_license(self, me_mock, product_mock, verified_mock):
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state={NO_VALID_LICENSE}')
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
@patch.object(HepClient, 'fetch_eorders', return_value=[])
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=ME_DATA)
|
||||
def test_user_can_login_local_license_invalid(self, me_mock, product_mock, verified_mock):
|
||||
now = timezone.now()
|
||||
expiry_date = now - timedelta(1)
|
||||
LicenseFactory(expire_date=expiry_date, licensee=self.user, for_role=self.teacher_role).save()
|
||||
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state={NO_VALID_LICENSE}')
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
@patch.object(requests, 'get', return_value=MockResponse(500))
|
||||
def test_user_gets_notified_if_server_error(self, post_mock):
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state={UNKNOWN_ERROR}')
|
||||
|
||||
@patch.object(HepClient, 'fetch_eorders', return_value=VALID_TEACHERS_ORDERS)
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=NEW_ME_DATA)
|
||||
def new_user_is_created_in_system_after_login_with_valid_license(self, user_mock, authorize_mock, orders_mock):
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
|
||||
try:
|
||||
user = User.objects.get(email=NEW_ME_DATA['email'])
|
||||
except:
|
||||
self.fail('LoginTests.new_user_is_created_in_system_after_login: User was not created')
|
||||
|
||||
user_role_key = user.user_roles.get(user=user).role.key
|
||||
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
|
||||
|
||||
license = License.objects.get(licensee=user)
|
||||
self.assertEqual(license.for_role.key, Role.objects.TEACHER_KEY)
|
||||
|
||||
school_class = SchoolClass.objects.get(users__in=[user])
|
||||
self.assertIsNotNone(school_class)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state=success')
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
try:
|
||||
UserData.objects.get(user=user)
|
||||
self.fail('LoginTests.test_teacher_can_login_with_valid_license: Userdata should not exist')
|
||||
except:
|
||||
pass
|
||||
|
||||
@patch.object(HepClient, 'fetch_eorders', return_value=[])
|
||||
@patch.object(DjangoRemoteApp, 'authorize_access_token', return_value=TOKEN)
|
||||
@patch.object(HepClient, 'user_details', return_value=NEW_ME_DATA)
|
||||
def new_user_is_created_in_system_after_login(self, user_mock, authorize_mock, orders_mock):
|
||||
response = self._login('/api/oauth/authorize?code=1234')
|
||||
|
||||
try:
|
||||
user = User.objects.get(email=NEW_ME_DATA['email'])
|
||||
except:
|
||||
self.fail('LoginTests.new_user_is_created_in_system_after_login: User was not created')
|
||||
|
||||
user_role_key = user.user_roles.get(user=user).role.key
|
||||
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
|
||||
|
||||
license = License.objects.get(licensee=user)
|
||||
self.assertEqual(license.for_role.key, Role.objects.TEACHER_KEY)
|
||||
|
||||
school_class = SchoolClass.objects.get(users__in=[user])
|
||||
self.assertIsNotNone(school_class)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(response.url, f'/{OAUTH_REDIRECT}?state={NO_VALID_LICENSE}')
|
||||
self.assertTrue(self.user.is_authenticated)
|
||||
|
||||
try:
|
||||
UserData.objects.get(user=user)
|
||||
self.fail('LoginTests.test_teacher_can_login_with_valid_license: Userdata should not exist')
|
||||
except:
|
||||
pass
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
|
|
@ -8,11 +9,19 @@ from core.utils import is_private_api_call_allowed
|
|||
|
||||
|
||||
class MiddlewareTestCase(TestCase):
|
||||
def test_user_without_hep_id_cannot_see_private_api(self):
|
||||
|
||||
user = get_user_model().objects.create_user(username='sme')
|
||||
|
||||
body = b'"{mutation {\\n addRoom}"'
|
||||
|
||||
self.assertFalse(is_private_api_call_allowed(user, body))
|
||||
|
||||
def test_user_with_license_can_see_private_api(self):
|
||||
|
||||
tomorrow = timezone.now() + timedelta(1)
|
||||
user = UserFactory(username='aschiman@ch.ch')
|
||||
user.license_expiry_date = tomorrow
|
||||
user.license_expiry_date = tomorrow.date()
|
||||
|
||||
body = b'"{mutation {\\n addRoom}"'
|
||||
|
||||
|
|
@ -39,7 +48,7 @@ class MiddlewareTestCase(TestCase):
|
|||
def test_logout_is_allowed_without_valid_license(self):
|
||||
|
||||
yesterday = timezone.now() - timedelta(1)
|
||||
user = UserFactory(username='aschiman@ch.ch')
|
||||
user = UserFactory(username='aschiman@ch.ch', hep_id=34)
|
||||
user.license_expiry_date = yesterday.date()
|
||||
|
||||
body = b'"{mutation { logout {"'
|
||||
|
|
@ -49,7 +58,7 @@ class MiddlewareTestCase(TestCase):
|
|||
def test_me_query_is_allowed_without_valid_license(self):
|
||||
|
||||
yesterday = timezone.now() - timedelta(1)
|
||||
user = UserFactory(username='aschiman@ch.ch')
|
||||
user = UserFactory(username='aschiman@ch.ch', hep_id=34)
|
||||
user.license_expiry_date = yesterday
|
||||
|
||||
body = b'"{query { me {"'
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
from core.factories import UserFactory
|
||||
from oauth.factories import Oauth2TokenFactory
|
||||
|
||||
|
||||
REFRESH_DATA = {
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 31536000,
|
||||
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI5MzU3NDdmZC1mODM0LTQ0Y2MtODY0NC01YjA0ZGY2N2M4ZTMiLCJqdGkiOiI5ZmI5ZWFmOWM5Y2M4YWM4ZThlMDkyMDlkNWQwZjQxNTRiZTYwNmQ4ZDdkOTU0OTVkNTM0OWU3NWJmNDJiZGQ3MWRhYWU5MTJmMDIwNGMxNiIsImlhdCI6MTYyMzkyNjI1MC4xMzkxNDYsIm5iZiI6MTYyMzkyNjI1MC4xMzkxNTUsImV4cCI6MTY1NTQ2MjI1MC4xMDM2NTksInN1YiI6IjI5Iiwic2NvcGVzIjpbIm9yZGVycyJdLCJncm91cHMiOlsidGVzdHVzZXIiLCJ0ZWFjaGVyIl19.mdj0xP-GpPbwFt6VpnGq1RJND9SbfutcQkVv0I3G8HNEVylf17FuK22CMJRZLN2BW6hV67Kpps7RoCBPh9XWYUkkpLA1lD3RBZEit2IdBOhXf6G8B8go_UV2B8BUgHNn0AyLVWsawtdPkcIXbPkXv0oQAi-tsqFan5OE0XPQCUJfun2Cvhe4Teyl98-5zd_njt6mK_0BNtnDDAWjMTgVh9y_-WTu34S_2xttlh-vCFYMl8JwZPuNpTrCyD_UqfY8sp_dKPyg87BLRk4uR1iFoL399BvMSIUXoLFdh7Hb6eMuSBQH63JM77zuWk2bACBofULE2ajsbQg9a8dL43inNRwtRDlhofaw1NHYF_TrzRBP2pRgbo8FsEONVx9FRocMdfo4-icR1_Pb59Rr9lmiEu5JAi47o0rRCz9lAuUiHdliZtEPyAUQXJ5-y0zOITko83VstsU88OodgGvwZ53yp_aibdDBuX99YOSRvlBFXH0Sst49PEvGWnnNRP_4KOtAOzJ7n9yE0cDWo-VgB97KOVEv_BhiiE0SMbeYe7ByT8u9lNwKGX3AYWQTsbO5IlKn9f86NKBeLAB5bWaXXNnsQreNrTlhky8LoUQBtrSdwNWR7ZUheQOlSBKvqhT_48lJU_CMNxx38rmaoG6qC_WNKcFq_Lb01hLZ_VvYOPaIlWw",
|
||||
"refresh_token": "def502007c4d524d828b97e4dc75198a482b3cf7db88dcef78b9c2bfe008ec8db48f02d564366b4f6d41115b090411bdc6c185fe06195aad2ef4e01cd6a7e4d87144eccf628c3864d669ec8fcebbaf2eb62fb42b367650e4dea8dbcc9465f5cf1ccb3e44ff3066f2ced4aac455677fd95f3abf36536f1828eb24c0c173858ca593a9d855ede03c7ec5a900930ca10b0a6270358ba114e3f695b5adfc7c51d0a67b4b29a617015213ae4193f28c9c2d8f8504a8573240085c948c6dc9654bc5f0d7447e22bf25278135005ba51f953a056cf8f64f55fe15447ba395160ce1e03f6861684763d0de11bc976d71f545548451539d2025b74693010661d1d3a7886644fc952f3adeb6f05350489f1cebdf947bca96112ccdb8ffd2b5b5d0a843ff2d3835d9cbee1f7442c8c5ba41d028f2e3b83523e510084c3c54f672820c92781aa63ce8a7cb9a938ba30562b8e69e23ab500a6f90eaa98a3e2ab40af88d20fc8a3c23185eab949d81ea9d737798aa022a03b8c1e5f987c77345cf5582debb726171484b1502871a8f95ce90e5d6"
|
||||
}
|
||||
|
||||
|
||||
class OAuth2TokenTestCases(TestCase):
|
||||
def setUp(self):
|
||||
user = UserFactory(username='housi')
|
||||
self.token = Oauth2TokenFactory(user=user)
|
||||
self.now = timezone.now()
|
||||
|
||||
def test_is_valid(self):
|
||||
self.assertFalse(self.token.has_expired())
|
||||
|
||||
def test_has_expired(self):
|
||||
one_hourish_delta_in_ms = 60*60
|
||||
self.token.expires_at -= one_hourish_delta_in_ms
|
||||
self.token.save()
|
||||
self.assertTrue(self.token.has_expired())
|
||||
|
||||
def test_can_update_refresh_data(self):
|
||||
token, success = self.token.update_with_refresh_data(REFRESH_DATA)
|
||||
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(REFRESH_DATA['access_token'], token.access_token)
|
||||
|
||||
def test_success_on_update_refresh_data(self):
|
||||
token, success = self.token.update_with_refresh_data(REFRESH_DATA)
|
||||
|
||||
self.assertTrue(success)
|
||||
self.assertEqual(REFRESH_DATA['access_token'], token.access_token)
|
||||
|
||||
def test_fail_on_update_refresh_data(self):
|
||||
data = REFRESH_DATA.copy()
|
||||
data['access_token'] = '12344'
|
||||
token, success = self.token.update_with_refresh_data(data)
|
||||
|
||||
self.assertFalse(success)
|
||||
self.assertIsNone(token)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
from django.conf.urls import url
|
||||
from oauth import views
|
||||
|
||||
app_name = 'users'
|
||||
urlpatterns = [
|
||||
url(r'^login/', views.login, name='login'),
|
||||
url(r'^callback/', views.authorize, name='authorize')
|
||||
]
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
from core.hep_client import HepClient, HepClientException
|
||||
from core.models import AdminData
|
||||
from oauth.hep_client import HepClient, HepClientException
|
||||
from users.models import License
|
||||
from users.models import User, UserRole, Role, SchoolClass
|
||||
|
||||
|
|
@ -9,13 +8,10 @@ UNKNOWN_ERROR = 'unknown_error'
|
|||
NO_VALID_LICENSE = 'no_valid_license'
|
||||
|
||||
|
||||
def handle_user_and_verify_products(user_data):
|
||||
def handle_user_and_verify_products(user_data, token):
|
||||
hep_client = HepClient()
|
||||
|
||||
try:
|
||||
user = User.objects.get(hep_id=user_data['id'])
|
||||
except User.DoesNotExist:
|
||||
user = User.objects.create_user_from_hep(user_data)
|
||||
user = User.objects.get_or_create_hep_user(user_data)
|
||||
|
||||
try:
|
||||
if not hep_client.is_email_verified(user_data):
|
||||
|
|
@ -26,7 +22,7 @@ def handle_user_and_verify_products(user_data):
|
|||
license = License.objects.get_active_license_for_user(user)
|
||||
|
||||
if not license:
|
||||
license, error_msg = check_and_create_licenses(hep_client, user)
|
||||
license, error_msg = check_and_create_licenses(hep_client, user, token)
|
||||
|
||||
if error_msg:
|
||||
return user, error_msg
|
||||
|
|
@ -39,10 +35,9 @@ def handle_user_and_verify_products(user_data):
|
|||
return user, None
|
||||
|
||||
|
||||
def check_and_create_licenses(hep_client, user):
|
||||
def check_and_create_licenses(hep_client, user, token):
|
||||
try:
|
||||
admin_token = AdminData.objects.get_admin_token()
|
||||
product = hep_client.myskillbox_product_for_customer(admin_token, user.hep_id)
|
||||
product = hep_client.active_myskillbox_product_for_customer(token_dict=token)
|
||||
except HepClientException:
|
||||
return None, UNKNOWN_ERROR
|
||||
|
||||
|
|
@ -50,7 +45,6 @@ def check_and_create_licenses(hep_client, user):
|
|||
license = License.objects.create_license_for_role(user, product['activated'], product['raw'],
|
||||
product['license']['edition'],
|
||||
product['order_id'], product['isbn'])
|
||||
# todo handle no license case
|
||||
else:
|
||||
return None, NO_VALID_LICENSE
|
||||
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
from authlib.integrations.base_client import OAuthError
|
||||
from django.conf import settings
|
||||
from django.shortcuts import redirect
|
||||
from sentry_sdk import capture_exception
|
||||
|
||||
from oauth.hep_client import HepClient
|
||||
from oauth.oauth_client import oauth
|
||||
from oauth.models import OAuth2Token
|
||||
from oauth.user_signup_login_handler import handle_user_and_verify_products, EMAIL_NOT_VERIFIED, UNKNOWN_ERROR
|
||||
from django.contrib.auth import login as dj_login
|
||||
|
||||
from core.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
OAUTH_REDIRECT = 'oauth-redirect'
|
||||
|
||||
|
||||
def login(request):
|
||||
hep_oauth_client = oauth.create_client('hep')
|
||||
redirect_uri = settings.OAUTH_LOCAL_REDIRECT_URI
|
||||
return hep_oauth_client.authorize_redirect(request, redirect_uri)
|
||||
|
||||
|
||||
def authorize(request):
|
||||
hep_client = HepClient()
|
||||
try:
|
||||
token = oauth.hep.authorize_access_token(request)
|
||||
user_data = hep_client.user_details(token_dict=token)
|
||||
|
||||
user, status_msg = handle_user_and_verify_products(user_data, token)
|
||||
user.sync_with_hep_data(user_data)
|
||||
except OAuthError as e:
|
||||
logger.warning(f'OAuth error: {e}')
|
||||
if not settings.DEBUG:
|
||||
capture_exception(e)
|
||||
return redirect(f'/{OAUTH_REDIRECT}?state={UNKNOWN_ERROR}')
|
||||
|
||||
if user and status_msg != EMAIL_NOT_VERIFIED:
|
||||
dj_login(request, user)
|
||||
|
||||
OAuth2Token.objects.update_or_create_token(token, user)
|
||||
|
||||
if status_msg:
|
||||
return redirect(f'/{OAUTH_REDIRECT}?state={status_msg}')
|
||||
|
||||
return redirect(f'/{OAUTH_REDIRECT}?state=success')
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UserConfig(AppConfig):
|
||||
name = 'registration'
|
||||
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# Generated by Django 2.0.6 on 2019-10-09 09:05
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('users', '0009_auto_20191009_0905'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='License',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LicenseType',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='License name')),
|
||||
('key', models.CharField(max_length=128)),
|
||||
('active', models.BooleanField(default=False, verbose_name='License active')),
|
||||
('description', models.TextField(default='', verbose_name='Description')),
|
||||
('for_role', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='users.Role')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='license',
|
||||
name='license_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='registration.LicenseType'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='license',
|
||||
name='licensee',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 2.0.6 on 2019-10-10 09:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='licensetype',
|
||||
name='key',
|
||||
field=models.CharField(max_length=128, unique=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
# Generated by Django 2.0.6 on 2020-02-04 13:31
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0010_schoolclass_code'),
|
||||
('registration', '0002_auto_20191010_0905'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='licensetype',
|
||||
name='for_role',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='license',
|
||||
name='license_type',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='license',
|
||||
name='expire_date',
|
||||
field=models.DateField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='license',
|
||||
name='for_role',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.Role'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='license',
|
||||
name='raw',
|
||||
field=models.TextField(default=''),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='LicenseType',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
# Generated by Django 2.1.15 on 2020-02-20 10:39
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('registration', '0003_auto_20200204_1331'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='license',
|
||||
name='for_role',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='license',
|
||||
name='licensee',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='License',
|
||||
),
|
||||
]
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
import graphene
|
||||
from django.contrib.auth import login
|
||||
from graphene import relay
|
||||
|
||||
from core.hep_client import HepClient, HepClientException
|
||||
from core.models import AdminData
|
||||
from users.user_signup_login_handler import handle_user_and_verify_products, UNKNOWN_ERROR, NO_VALID_LICENSE
|
||||
|
||||
|
||||
class Registration(relay.ClientIDMutation):
|
||||
class Input:
|
||||
confirmation_key = graphene.String()
|
||||
user_id = graphene.Int()
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
confirmation_key = kwargs.get('confirmation_key')
|
||||
user_id = kwargs.get('user_id')
|
||||
|
||||
hep_client = HepClient()
|
||||
admin_token = AdminData.objects.get_admin_token()
|
||||
|
||||
try:
|
||||
hep_client.customer_activate(confirmation_key, user_id)
|
||||
user_data = hep_client.customers_by_id(admin_token, user_id)
|
||||
# double check if user has verified his email. If the "confirmation" field is present, the email address
|
||||
# is not verified.
|
||||
if 'confirmation' in user_data:
|
||||
return cls.return_fail_registration_msg('invalid_key')
|
||||
except HepClientException:
|
||||
return cls.return_fail_registration_msg('unknown_error')
|
||||
|
||||
user, status_msg = handle_user_and_verify_products(user_data)
|
||||
|
||||
if user:
|
||||
login(info.context, user)
|
||||
|
||||
if status_msg:
|
||||
if status_msg == NO_VALID_LICENSE:
|
||||
return cls(success=True, message=NO_VALID_LICENSE)
|
||||
else:
|
||||
return cls.return_fail_registration_msg(status_msg)
|
||||
|
||||
return cls(success=True, message='success')
|
||||
|
||||
@classmethod
|
||||
def return_fail_registration_msg(cls, message):
|
||||
if message == UNKNOWN_ERROR:
|
||||
raise Exception(message)
|
||||
|
||||
return cls(success=False, message=message)
|
||||
|
||||
|
||||
class RegistrationMutations:
|
||||
registration = Registration.Field()
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from django.conf import settings
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 2019-10-08
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.test import TestCase, RequestFactory
|
||||
from graphene.test import Client
|
||||
|
||||
from api.schema_public import schema
|
||||
from core.hep_client import HepClient
|
||||
from core.tests.mock_hep_data_factory import ME_DATA, VALID_TEACHERS_ORDERS
|
||||
from users.models import License
|
||||
from users.models import User, Role, SchoolClass
|
||||
|
||||
INVALID_KEY_ME = dict(ME_DATA)
|
||||
|
||||
INVALID_KEY_ME['confirmation'] = 'abddddddd'
|
||||
|
||||
|
||||
class RegistrationTests(TestCase):
|
||||
def setUp(self):
|
||||
request = RequestFactory().post('/')
|
||||
|
||||
Role.objects.create_default_roles()
|
||||
|
||||
self.email = 'sepp@skillbox.iterativ.ch'
|
||||
self.first_name = 'Sepp'
|
||||
self.last_name = 'Feuz'
|
||||
|
||||
# adding session
|
||||
middleware = SessionMiddleware()
|
||||
middleware.process_request(request)
|
||||
request.session.save()
|
||||
self.client = Client(schema=schema, context_value=request)
|
||||
|
||||
def make_register_mutation(self, confirmation_key, user_id):
|
||||
mutation = '''
|
||||
mutation Registration($input: RegistrationInput!){
|
||||
registration(input: $input) {
|
||||
success
|
||||
message
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
return self.client.execute(mutation, variables={
|
||||
'input': {
|
||||
'confirmationKey': confirmation_key,
|
||||
'userId': user_id
|
||||
}
|
||||
})
|
||||
|
||||
@patch.object(HepClient, 'customer_activate', return_value="Response")
|
||||
@patch.object(HepClient, 'customers_by_id', return_value=ME_DATA)
|
||||
@patch.object(HepClient, 'myskillbox_product_for_customer', return_value=None)
|
||||
@patch.object(HepClient, 'fetch_admin_token', return_value=b'"AABBCCDDEE**44566"')
|
||||
def test_user_can_register_with_valid_confirmation_key_and_no_license(self, admin_mock, customer_by_id_mock,
|
||||
product_mock, customer_mock):
|
||||
|
||||
result = self.make_register_mutation('CONFIRMATION_KEY', 1)
|
||||
|
||||
self.assertTrue(result.get('data').get('registration').get('success'))
|
||||
self.assertEqual(result.get('data').get('registration').get('message'), 'no_valid_license')
|
||||
|
||||
@patch.object(HepClient, 'customer_activate', return_value="Response")
|
||||
@patch.object(HepClient, 'customers_by_id', return_value=INVALID_KEY_ME)
|
||||
@patch.object(HepClient, 'fetch_admin_token', return_value=b'"AABBCCDDEE**44566"')
|
||||
def test_user_cannot_register_with_invalid_key(self, admin_mock, confirmation_mock, id_mock):
|
||||
|
||||
result = self.make_register_mutation('CONFIRMATION_KEY', 1)
|
||||
|
||||
self.assertFalse(result.get('data').get('registration').get('success'))
|
||||
self.assertEqual(result.get('data').get('registration').get('message'), 'invalid_key')
|
||||
|
||||
@patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS)
|
||||
@patch.object(HepClient, 'customer_activate', return_value="Response")
|
||||
@patch.object(HepClient, 'customers_by_id', return_value=ME_DATA)
|
||||
@patch.object(HepClient, 'fetch_admin_token', return_value=b'"AABBCCDDEE**44566"')
|
||||
def test_teacher_can_register_with_remote_license(self, admin_mock, id_mock, activate_mock, orders_mock):
|
||||
result = self.make_register_mutation('CONFIRMATION_KEY', 1)
|
||||
|
||||
user = User.objects.get(email=ME_DATA['email'])
|
||||
|
||||
user_role_key = user.user_roles.get(user=user).role.key
|
||||
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
|
||||
|
||||
license = License.objects.get(licensee=user)
|
||||
self.assertEqual(license.for_role.key, Role.objects.TEACHER_KEY)
|
||||
|
||||
school_class = SchoolClass.objects.get(users__in=[user])
|
||||
self.assertIsNotNone(school_class)
|
||||
|
||||
self.assertTrue(result.get('data').get('registration').get('success'))
|
||||
self.assertTrue(user.is_authenticated)
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 25.02.20
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
import json
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import requests
|
||||
from django.test import TestCase, Client
|
||||
from django.urls import reverse
|
||||
|
||||
from core.hep_client import HepClient
|
||||
from core.tests.mock_hep_data_factory import MockResponse
|
||||
|
||||
RESPONSE = {
|
||||
'id': 1234,
|
||||
'confirmation': 'abdc1234',
|
||||
'firstname': 'Pesche',
|
||||
'lastname': 'Zubrüti',
|
||||
'email': 'aschima@ch.ch',
|
||||
'prefix': 'Herr',
|
||||
'gender': 1,
|
||||
'addresses': [
|
||||
{
|
||||
'country_id': 'CH',
|
||||
'street': ['Weg 1'],
|
||||
'postcode': '1234',
|
||||
'city': 'Äussere Einöde',
|
||||
'firstname': 'Pesche',
|
||||
'lastname': 'Zubrüti',
|
||||
'prefix': 'Herr',
|
||||
'default_shipping': True,
|
||||
'default_billing': True,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
DATA = {
|
||||
'accepted_terms': True,
|
||||
'customer': {
|
||||
'firstname': 'Pesche',
|
||||
'lastname': 'Zubrüti',
|
||||
'email': 'aschima@ch.ch',
|
||||
'prefix': 'Herr',
|
||||
'gender': 1,
|
||||
'addresses': [
|
||||
{
|
||||
'country_id': 'CH',
|
||||
'street': ['Weg 1'],
|
||||
'postcode': '1234',
|
||||
'city': 'Äussere Einöde',
|
||||
'firstname': 'Pesche',
|
||||
'lastname': 'Zubrüti',
|
||||
'prefix': 'Herr',
|
||||
'default_shipping': True,
|
||||
'default_billing': True,
|
||||
}
|
||||
],
|
||||
'password': '123454abasfd'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ProxyTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
|
||||
@patch.object(HepClient, 'customer_create', return_value=RESPONSE)
|
||||
def test_proxy_filters_confirmation_key(self, create_mock):
|
||||
|
||||
response = self.client.post(reverse('api:registration:proxy'), json.dumps(DATA), content_type="application/json")
|
||||
found = 'confirmation' in response.json().keys()
|
||||
self.assertFalse(found)
|
||||
|
||||
@patch.object(requests, 'post', return_value=MockResponse(400,
|
||||
data={'message': 'Ein Kunde mit der gleichen E-Mail-Adresse existiert bereits in einer zugeordneten Website.'}))
|
||||
def test_handles_400(self, create_mock):
|
||||
|
||||
response = self.client.post(reverse('api:registration:proxy'), json.dumps(DATA), content_type="application/json")
|
||||
self.assertEquals(response.json()['message'], 'Ein Kunde mit der gleichen E-Mail-Adresse existiert bereits in einer zugeordneten Website.')
|
||||
|
||||
def test_requires_accepted_terms(self):
|
||||
|
||||
del DATA['accepted_terms']
|
||||
|
||||
response = self.client.post(reverse('api:registration:proxy'), json.dumps(DATA), content_type="application/json")
|
||||
self.assertEquals(response.status_code, 400)
|
||||
self.assertEquals(response.json()['message'], 'Sie müssen hier zustimmen, damit Sie sich registrieren können.')
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
from django.conf.urls import url
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from registration.view import RegistrationProxyView
|
||||
|
||||
app_name = 'registration'
|
||||
urlpatterns = [
|
||||
url(r'^registration/', csrf_exempt(RegistrationProxyView.as_view()), name="proxy"),
|
||||
]
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# ITerativ GmbH
|
||||
# http://www.iterativ.ch/
|
||||
#
|
||||
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
|
||||
#
|
||||
# Created on 25.02.20
|
||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||
import json
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.views import View
|
||||
|
||||
from core.hep_client import HepClient, HepClientException
|
||||
|
||||
|
||||
class RegistrationProxyView(View):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
hep_client = HepClient()
|
||||
data = json.loads(request.body)
|
||||
|
||||
if not self.terms_accepted(data):
|
||||
return JsonResponse(
|
||||
{
|
||||
'message': 'Sie müssen hier zustimmen, damit Sie sich registrieren können.'
|
||||
},
|
||||
status=400)
|
||||
|
||||
data['customer']['group_id'] = 5
|
||||
|
||||
try:
|
||||
hep_data = hep_client.customer_create(data)
|
||||
except HepClientException as e:
|
||||
return JsonResponse(e.args[1], status=e.args[0])
|
||||
|
||||
response_data = hep_data.copy()
|
||||
del response_data['confirmation']
|
||||
|
||||
return JsonResponse(response_data)
|
||||
|
||||
def terms_accepted(self, data):
|
||||
if 'accepted_terms' in data and data['accepted_terms']:
|
||||
del data['accepted_terms']
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
@ -1,8 +1,11 @@
|
|||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
import factory
|
||||
|
||||
from users.models import SchoolClass, SchoolClassMember, License, Team
|
||||
from django.utils.timezone import now
|
||||
|
||||
|
||||
class_types = ['DA', 'KV', 'INF', 'EE']
|
||||
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
||||
|
|
@ -44,3 +47,6 @@ class TeamFactory(factory.django.DjangoModelFactory):
|
|||
class LicenseFactory(factory.django.DjangoModelFactory):
|
||||
class Meta:
|
||||
model = License
|
||||
|
||||
expire_date = now() + timedelta(days=7)
|
||||
order_id = factory.Sequence(lambda n: n)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
TEACHER_KEY = 'teacher'
|
||||
STUDENT_KEY = 'student'
|
||||
|
||||
MYSKILLBOX_LICENSES = {
|
||||
"978-3-0355-1397-4": {
|
||||
'edition': STUDENT_KEY,
|
||||
'duration': 4 * 365,
|
||||
'name': 'Student 4 years'
|
||||
},
|
||||
"978-3-0355-1860-3": {
|
||||
'edition': STUDENT_KEY,
|
||||
'duration': 455,
|
||||
'name': 'Student 1 year'
|
||||
},
|
||||
"978-3-0355-1862-7": {
|
||||
'edition': STUDENT_KEY,
|
||||
'duration': 30,
|
||||
'name': 'Student test 1 month'
|
||||
},
|
||||
"978-3-0355-1861-0": {
|
||||
'edition': TEACHER_KEY,
|
||||
'duration': 30,
|
||||
'name': 'Teacher test 1 month'
|
||||
},
|
||||
"978-3-0355-1823-8": {
|
||||
'edition': TEACHER_KEY,
|
||||
'duration': 455,
|
||||
'name': 'Teacher 1 year'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def is_myskillbox_product(isbn):
|
||||
valid_isbns = list(MYSKILLBOX_LICENSES.keys())
|
||||
return isbn in valid_isbns
|
||||
|
|
@ -1 +0,0 @@
|
|||
from django.conf import settings
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
|
||||
from core.hep_client import HepClient
|
||||
from core.models import AdminData
|
||||
from users.models import User, License
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"Update licenses via cronjob"
|
||||
|
||||
hep_client = HepClient()
|
||||
|
||||
admin_token = AdminData.objects.get_admin_token()
|
||||
hep_users = User.objects.filter(hep_id__isnull=False)
|
||||
|
||||
for hep_user in hep_users:
|
||||
product = hep_client.myskillbox_product_for_customer(admin_token, hep_user.hep_id)
|
||||
|
||||
if product and License.objects.filter(licensee=hep_user, order_id=product['order_id']).count() == 0:
|
||||
license = License.objects.create_license_for_role(hep_user, product['activated'], product['raw'],
|
||||
product['license']['edition'], product['order_id'],
|
||||
product['isbn'])
|
||||
|
||||
if license.is_valid():
|
||||
hep_user.license_expiry_date = license.expire_date
|
||||
hep_user.save()
|
||||
|
|
@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import UserManager as DjangoUserManager
|
||||
|
||||
from core.hep_client import MYSKILLBOX_LICENSES
|
||||
from users.licenses import MYSKILLBOX_LICENSES
|
||||
|
||||
|
||||
class RoleManager(models.Manager):
|
||||
|
|
@ -113,16 +113,25 @@ class UserManager(DjangoUserManager):
|
|||
user = self.model.objects.get(email=user_data['email'])
|
||||
user.set_unusable_password()
|
||||
except self.model.DoesNotExist:
|
||||
user = self._create_user_with_random_password_no_save( user_data['firstname'],
|
||||
user_data['lastname'],
|
||||
user = self._create_user_with_random_password_no_save(user_data['first_name'],
|
||||
user_data['last_name'],
|
||||
user_data['email'])
|
||||
|
||||
user.hep_id = user_data['id']
|
||||
user.hep_group_id = user_data['group_id']
|
||||
user.save()
|
||||
|
||||
if user.hep_group_id == settings.HEP_MYSKILLBOX_GROUP_ID:
|
||||
apps.get_model('users.UserData').objects.create(user=user, accepted_terms=True)
|
||||
# todo: how to handle
|
||||
# if user.hep_group_id == settings.HEP_MYSKILLBOX_GROUP_ID:
|
||||
# apps.get_model('users.UserData').objects.create(user=user, accepted_terms=True)
|
||||
|
||||
return user
|
||||
|
||||
def get_or_create_hep_user(self, user_data):
|
||||
|
||||
try:
|
||||
user = self.get(hep_id=user_data['id'])
|
||||
except self.model.DoesNotExist:
|
||||
user = self.create_user_from_hep(user_data)
|
||||
|
||||
return user
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import random
|
||||
import re
|
||||
from datetime import datetime, timedelta, date
|
||||
import string
|
||||
from datetime import date, datetime
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AbstractUser, Permission
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db import models
|
||||
from django.utils.timezone import make_aware, is_aware
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
|
||||
from core.hep_client import HepClient, MYSKILLBOX_LICENSES
|
||||
from users.licenses import MYSKILLBOX_LICENSES
|
||||
from users.managers import RoleManager, UserRoleManager, UserManager, LicenseManager
|
||||
|
||||
DEFAULT_SCHOOL_ID = 1
|
||||
|
|
@ -94,12 +96,12 @@ class User(AbstractUser):
|
|||
self.username = hep_data['email']
|
||||
data_has_changed = True
|
||||
|
||||
if self.first_name != hep_data['firstname']:
|
||||
self.first_name = hep_data['firstname']
|
||||
if self.first_name != hep_data['first_name']:
|
||||
self.first_name = hep_data['first_name']
|
||||
data_has_changed = True
|
||||
|
||||
if self.last_name != hep_data['lastname']:
|
||||
self.last_name = hep_data['lastname']
|
||||
if self.last_name != hep_data['last_name']:
|
||||
self.last_name = hep_data['last_name']
|
||||
data_has_changed = True
|
||||
|
||||
if data_has_changed:
|
||||
|
|
@ -304,8 +306,17 @@ class License(models.Model):
|
|||
return self.for_role.key == RoleManager.TEACHER_KEY
|
||||
|
||||
def is_valid(self):
|
||||
return HepClient.is_product_active(
|
||||
datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day), self.isbn)
|
||||
date = make_aware(datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day))
|
||||
return License.is_product_active(date, self.isbn)
|
||||
|
||||
@staticmethod
|
||||
def is_product_active(expiry_date, isbn):
|
||||
now = timezone.now()
|
||||
|
||||
if not is_aware(expiry_date):
|
||||
expiry_date = make_aware(expiry_date)
|
||||
|
||||
return expiry_date >= now >= expiry_date - timedelta(days=MYSKILLBOX_LICENSES[isbn]['duration'])
|
||||
|
||||
def __str__(self):
|
||||
return f'License for role: {self.for_role}'
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ from django.conf import settings
|
|||
from django.contrib.auth import authenticate, login
|
||||
from graphene import relay
|
||||
|
||||
from core.hep_client import HepClient, HepClientUnauthorizedException, HepClientException
|
||||
from users.user_signup_login_handler import handle_user_and_verify_products, UNKNOWN_ERROR, EMAIL_NOT_VERIFIED
|
||||
# from users.user_signup_login_handler import handle_user_and_verify_products, UNKNOWN_ERROR, EMAIL_NOT_VERIFIED
|
||||
|
||||
|
||||
class BetaLogin(relay.ClientIDMutation):
|
||||
|
|
@ -30,46 +29,7 @@ class BetaLogin(relay.ClientIDMutation):
|
|||
raise Exception('not_implemented')
|
||||
|
||||
|
||||
class Login(relay.ClientIDMutation):
|
||||
class Input:
|
||||
token_input = graphene.String()
|
||||
|
||||
success = graphene.Boolean()
|
||||
message = graphene.String()
|
||||
|
||||
@classmethod
|
||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||
|
||||
hep_client = HepClient()
|
||||
token = kwargs.get('token_input')
|
||||
|
||||
try:
|
||||
user_data = hep_client.customer_me(token)
|
||||
except HepClientUnauthorizedException:
|
||||
return cls.return_login_message('invalid_credentials')
|
||||
except HepClientException:
|
||||
return cls.return_login_message(UNKNOWN_ERROR)
|
||||
|
||||
user, status_msg = handle_user_and_verify_products(user_data)
|
||||
user.sync_with_hep_data(user_data)
|
||||
|
||||
if user and status_msg != EMAIL_NOT_VERIFIED:
|
||||
login(info.context, user)
|
||||
|
||||
if status_msg:
|
||||
return cls.return_login_message(status_msg)
|
||||
|
||||
return cls(success=True, message='success')
|
||||
|
||||
@classmethod
|
||||
def return_login_message(cls, message):
|
||||
if message == EMAIL_NOT_VERIFIED or message == UNKNOWN_ERROR or message == 'invalid_credentials':
|
||||
raise Exception(message)
|
||||
return cls(success=True, message=message)
|
||||
|
||||
|
||||
class UserMutations:
|
||||
login = Login.Field()
|
||||
beta_login = BetaLogin.Field()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.utils.timezone import now
|
||||
|
||||
from core.factories import UserFactory
|
||||
from users.factories import SchoolClassFactory
|
||||
from users.factories import SchoolClassFactory, LicenseFactory
|
||||
from users.licenses import MYSKILLBOX_LICENSES
|
||||
from users.models import Role, UserRole, DEFAULT_SCHOOL_ID
|
||||
|
||||
|
||||
|
|
@ -47,6 +52,9 @@ def create_users(data=None):
|
|||
)
|
||||
|
||||
else:
|
||||
in_a_week = now() + timedelta(days=7)
|
||||
hep_id = 1
|
||||
|
||||
for school_class in data:
|
||||
first, last = school_class.get('teacher')
|
||||
teacher = UserFactory(
|
||||
|
|
@ -54,19 +62,26 @@ def create_users(data=None):
|
|||
first_name=first,
|
||||
last_name=last,
|
||||
email='{}.{}@skillbox.example'.format(first, last).lower(),
|
||||
onboarding_visited=True
|
||||
onboarding_visited=True,
|
||||
license_expiry_date=in_a_week,
|
||||
hep_id=hep_id
|
||||
)
|
||||
UserRole.objects.create(user=teacher, role=teacher_role)
|
||||
|
||||
students = []
|
||||
|
||||
for first, last in school_class.get('students'):
|
||||
hep_id += 1
|
||||
student = create_student(
|
||||
username='{}.{}'.format(first, last).lower(),
|
||||
first_name=first,
|
||||
last_name=last,
|
||||
email='{}.{}@skillbox.example'.format(first, last).lower(),
|
||||
onboarding_visited=True
|
||||
onboarding_visited=True,
|
||||
license_expiry_date=in_a_week,
|
||||
hep_id=hep_id
|
||||
)
|
||||
|
||||
students.append(student)
|
||||
|
||||
SchoolClassFactory(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"id": 5,
|
||||
"email": "hans.meier@iterativ.ch",
|
||||
"email_verified_at": 123456789,
|
||||
"salutation": null,
|
||||
"company": null,
|
||||
"first_name": "Hans",
|
||||
"last_name": "Meier",
|
||||
"phone": null,
|
||||
"institution": null,
|
||||
"name": "Hans Meier"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue