Compare commits

..

679 Commits

Author SHA1 Message Date
niggl 88844e1a44 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-26 15:53:45 +00:00
niggl e76a9cef95 Merge pull request 'Release 0.7.1' (#173) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #173
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-26 15:52:41 +00:00
niggl 3ef3a94b20 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-26 14:24:07 +00:00
niggl 135852eb9a 🚀Bumped version to v0.7.1
continuous-integration/drone/push Build is passing
2021-03-26 15:23:05 +01:00
niggl 963253cbc8 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-26 14:22:55 +00:00
niggl 539a6509b1 Merge pull request 'RESPONSERUNNERCARD fix bugfix/171-responserunnercards' (#172) from bugfix/171-responserunnercards into dev
continuous-integration/drone/push Build is failing
Thx @philipp :)

Reviewed-on: #172
2021-03-26 14:22:15 +00:00
niggl f3d73d5346 Tests now keep the group
continuous-integration/drone/pr Build is passing
ref #171
2021-03-26 15:17:05 +01:00
niggl f159252651 Revert "Set timeout even higher b/c sqlite just kills itself during these tests"
continuous-integration/drone/pr Build is failing
This reverts commit 6ab60998d4.
2021-03-26 15:14:17 +01:00
niggl 6ab60998d4 Set timeout even higher b/c sqlite just kills itself during these tests
continuous-integration/drone/pr Build is failing
ref #171
2021-03-26 15:13:31 +01:00
niggl 30d220bc36 Adjusted jest timeout to mitigate sqlite from invalidateing all tests⏱
continuous-integration/drone/pr Build is failing
ref #171
2021-03-26 15:06:20 +01:00
niggl 24aff3bac4 Now resolveing runnercards
ref #171
2021-03-26 14:56:21 +01:00
niggl ce63043887 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-23 17:50:46 +00:00
niggl e40017a6b8 Merge pull request 'Release 0.7.0' (#170) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #170
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-23 17:49:45 +00:00
niggl e843a464e7 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is failing
2021-03-23 17:44:04 +00:00
niggl d0ae50d557 🚀Bumped version to v0.7.0
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-03-23 18:42:16 +01:00
niggl 7a49e7c5c9 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-23 17:42:14 +00:00
niggl 1dd64204cc Merge pull request 'Bulk card creation feature/168-runnercards_bulk' (#169) from feature/168-runnercards_bulk into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #169
2021-03-23 17:41:31 +00:00
niggl 438ff0fc3f Added bulk card creation tests
continuous-integration/drone/pr Build is passing
ref #168
2021-03-23 18:19:49 +01:00
niggl c1bbda51f0 Added new "bulk" endpoint
ref #168
2021-03-23 18:16:55 +01:00
niggl 4705a39aab 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-19 16:41:43 +00:00
niggl 4d721f62d9 Merge pull request 'Release 0.6.4' (#167) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #167
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-19 16:40:45 +00:00
niggl b0328ffdaf 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-19 16:34:14 +00:00
niggl 031cede542 🚀Bumped version to v0.6.4
continuous-integration/drone/push Build is passing
2021-03-19 17:33:17 +01:00
niggl 3c69f8c4a8 Adjsuted endpoint 2021-03-19 17:32:46 +01:00
niggl cc6568c381 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-18 15:54:33 +00:00
niggl a3a1395a46 Merge pull request 'Release 0.6.3' (#165) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #165
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-18 15:53:42 +00:00
niggl b08acc6660 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-18 15:11:01 +00:00
niggl 7a303c2b2c 🚀Bumped version to v0.6.3
continuous-integration/drone/push Build is passing
2021-03-18 16:10:02 +01:00
niggl 3f9a7049e3 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-18 15:09:50 +00:00
niggl 6249419fae Merge pull request 'TrackScan Update bug 🐞bugfix/163-trackscan_updates' (#164) from bugfix/163-trackscan_updates into dev
continuous-integration/drone/push Build is failing
Reviewed-on: #164
2021-03-18 15:09:08 +00:00
niggl f347b7ad49 Updated tests 🧪
continuous-integration/drone/pr Build is passing
ref #163
2021-03-18 14:05:13 +01:00
niggl 74faec85c8 Merge branch 'bugfix/163-trackscan_updates' of git.odit.services:lfk/backend into bugfix/163-trackscan_updates 2021-03-18 14:00:16 +01:00
niggl fbdadbef1f The basic bugfix 🐞
ref #163
2021-03-18 14:00:14 +01:00
niggl c87c97c90f The basic bugfix 🐞
REF '!&§
2021-03-18 14:00:04 +01:00
niggl a6bca59ffe 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-17 18:44:42 +00:00
niggl 732a1b88d9 Merge pull request 'Release 0.6.2' (#162) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #162
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-17 18:43:45 +00:00
niggl 4c960feeb2 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-17 18:42:50 +00:00
niggl 72fee96a08 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is passing
2021-03-17 19:41:41 +01:00
niggl fcb43f92b0 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-17 18:41:40 +00:00
niggl 5ba8f1dd44 🚀Bumped version to v0.6.2 2021-03-17 19:41:35 +01:00
niggl 3d3790c2eb Merge pull request 'Bugfixes for trackscans feature/160-responseTrackScan_total_distance' (#161) from feature/160-responseTrackScan_total_distance into dev
continuous-integration/drone/push Build is failing
Reviewed-on: #161
2021-03-17 18:40:47 +00:00
niggl 1fa3fa75ee Fixed wrong error type 👀👀
continuous-integration/drone/pr Build is passing
ref #160
2021-03-17 19:37:35 +01:00
niggl c8882ae6a1 Removed duplicate openapi declarations 🗑
continuous-integration/drone/pr Build is failing
ref #160
2021-03-17 19:33:44 +01:00
niggl 673e896aa3 Added missing discription
ref #160
2021-03-17 19:31:09 +01:00
niggl 0ed7f78b2c Fixed missing renameing🛠
ref #160
2021-03-17 19:30:08 +01:00
niggl 1d38d308ad Changed the method of getting a parameter from the headers🛠
ref #160
2021-03-17 19:29:12 +01:00
niggl d709ee7479 Now defining security per endpoint 🔐
ref #160
2021-03-17 19:25:49 +01:00
niggl aae042c041 Now auto-etting the station token🔥🔥🔥
ref #160
2021-03-17 19:24:25 +01:00
niggl ca7a84eb3e Merge branch 'feature/160-responseTrackScan_total_distance' of git.odit.services:lfk/backend into feature/160-responseTrackScan_total_distance 2021-03-17 19:22:09 +01:00
niggl 1f32ed0727 Marked station as optional (quality of life improvements incoming)
ref #160
2021-03-17 19:22:04 +01:00
niggl 289f9e2196 Added comments✏
ref #160
2021-03-17 19:21:35 +01:00
niggl 937a9fad4d Added comments✏
ref #150
2021-03-17 19:21:29 +01:00
niggl 7c3a1b8fff Merge branch 'dev' into feature/160-responseTrackScan_total_distance 2021-03-17 19:16:54 +01:00
niggl a8ea4fa659 Fixed trackscan vaildation
ref #160
2021-03-17 19:16:42 +01:00
niggl c1dd4518d1 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-17 17:59:10 +00:00
niggl bdc7bb67e7 Merge pull request 'Release v0.6.0' (#159) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #159
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-17 17:58:20 +00:00
niggl 54988ba0fe 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-17 16:41:01 +00:00
niggl ce3ca9f1c8 🚀Bumped version to v0.6.1
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-03-17 17:40:13 +01:00
niggl 46b7aceb0b Scanauth return objects 2021-03-17 17:40:01 +01:00
niggl 486e450a58 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-17 16:13:23 +00:00
niggl 623b5a1873 🚀Bumped version to v0.6.0
continuous-integration/drone/push Build is passing
2021-03-17 17:12:01 +01:00
niggl a7958eecd6 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-17 15:52:53 +00:00
niggl 13e839902c Merge pull request 'Scanstation "me" endpoint feature/157-scanstation_me' (#158) from feature/157-scanstation_me into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #158
2021-03-17 15:52:02 +00:00
niggl 94001a48f1 Updated description
continuous-integration/drone/pr Build is passing
ref #157
2021-03-17 16:50:48 +01:00
niggl 2cb7ec7317 As requested by @philpp
continuous-integration/drone/pr Build is passing
ref #157
2021-03-17 16:49:19 +01:00
niggl 757332ed2b Added tests for the new endpoint
continuous-integration/drone/pr Build is passing
ref #157
2021-03-17 11:51:31 +01:00
niggl 8ba7ee1d48 Now adding station id to headers of request for scan auth
ref #157
2021-03-17 11:45:40 +01:00
niggl c5178e0181 Added scanstation me endpoint
ref #157
2021-03-17 11:44:54 +01:00
niggl a1a94ec9da 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-15 15:03:59 +00:00
niggl f7af777104 Applied Docker MTU fix 🛠
continuous-integration/drone/push Build is passing
2021-03-15 16:03:15 +01:00
niggl 076aa87dba 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-12 19:21:08 +00:00
niggl ca6fa633a1 Revert "Switched normal images to chached registry"
continuous-integration/drone/push Build was killed
This reverts commit cba4455d53.
2021-03-12 20:20:46 +01:00
niggl 641e2aed52 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build was killed
2021-03-12 20:09:36 +01:00
niggl cba4455d53 Switched normal images to chached registry 2021-03-12 20:09:26 +01:00
niggl d5930f7c46 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-12 18:03:04 +00:00
niggl 5541ae6ebd Updated ci with new kubernetes secrets 🚀🚀🚀
continuous-integration/drone/push Build was killed
ref odit/org#12
2021-03-12 19:02:45 +01:00
niggl 6c43872198 Changed ci pipeline type to kubernetes
continuous-integration/drone/push Build encountered an error
ref odit/org#12 (comment)
2021-03-10 20:07:46 +01:00
niggl e4ed20da3e 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-06 13:22:37 +00:00
niggl cb6e78fc17 Merge pull request 'selfservice forgotten mails feature/154-selfservice_forgotten' (#155) from feature/154-selfservice_forgotten into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #155
2021-03-06 13:22:16 +00:00
niggl bf1ec976e3 Added selfservice forgott positive tests
continuous-integration/drone/pr Build was killed
ref #154
2021-03-06 14:12:46 +01:00
niggl d0a7e34de8 Added all "negative" tests
ref #154
2021-03-06 14:08:39 +01:00
niggl 08957d4dc2 Updated to new responsetype
ref #154
2021-03-06 14:02:58 +01:00
niggl 1d762f5662 Renamed test
ref #154
2021-03-06 13:57:25 +01:00
niggl a95a9b4ec4 Added first selfservice forgotten test
ref #154
2021-03-06 13:56:55 +01:00
niggl e5dab3469c Changed endpoint url to avoid conflicts
ref #154
2021-03-06 13:56:34 +01:00
niggl c01233b4d6 Added console logging when a testing env get's discovered
ref #154
2021-03-06 13:51:24 +01:00
niggl 92920273be Adjusted tests for the new testing env
ref #154
2021-03-06 13:51:07 +01:00
niggl 6bb3ae8ba9 Mailer now ignores mailing erros when env is set to test
ref #154
2021-03-06 13:44:10 +01:00
niggl cedc1750c2 Added readme description for testing env
ref #154
2021-03-06 13:42:10 +01:00
niggl 3f372123fd Added testing env check
ref #154
2021-03-06 13:41:10 +01:00
niggl a3437475ca Runner controller now uses the Mailer functions
ref #154
2021-03-06 13:38:37 +01:00
niggl 83765136cc Added mailer functions
ref #154
2021-03-06 13:36:12 +01:00
niggl e26b7d4923 Implemented the "real" errors
ref #154
2021-03-06 13:32:36 +01:00
niggl e7f0cb45c9 Added not found error logic
ref #154
2021-03-06 13:29:44 +01:00
niggl ffcd45e572 Updated request timeout
ref #154
2021-03-06 13:24:43 +01:00
niggl d7099717c2 Created basic endpoint for user forgotten mails
ref #154
2021-03-06 13:23:01 +01:00
niggl 66d6023335 Added last reset requested timestamp to runners
ref #154
2021-03-06 13:15:27 +01:00
niggl 5f5c8a061e 📖New license file version [CI SKIP] [skip ci] 2021-03-04 16:27:55 +00:00
niggl bf71e35ecd 🧾New changelog file version [CI SKIP] [skip ci] 2021-03-04 16:23:18 +00:00
niggl 64da0eadb3 Merge pull request 'Alpha Release 0.5.0' (#153) from dev into main
continuous-integration/drone/tag Build is failing
continuous-integration/drone/push Build was killed
Reviewed-on: #153
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-03-04 16:22:36 +00:00
niggl 52728290b4 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-03-04 16:07:53 +00:00
niggl 3f2a2d2929 🚀Bumped version to v0.5.0
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build was killed
2021-03-04 17:03:51 +01:00
niggl f1d85cfb85 🚀Bumped version to v0.4.7 2021-03-04 17:03:41 +01:00
niggl 15356c1030 Merge pull request 'Features for the new selfservice feature/151-selfservice_scans_mails' (#152) from feature/151-selfservice_scans_mails into dev
continuous-integration/drone/push Build was killed
Reviewed-on: #152
2021-03-04 16:03:04 +00:00
niggl 82c65b632c Added scans returns 200 test
continuous-integration/drone/pr Build is passing
ref #151
2021-03-04 16:40:05 +01:00
niggl ae7d617690 Updated auth reset test for new mailer
ref #151
2021-03-04 16:35:30 +01:00
niggl bf6b70106e Now generateing bs mailer config in test env
ref #151
2021-03-04 16:32:06 +01:00
niggl 33310cdb44 Merge branch 'feature/151-selfservice_scans_mails' of git.odit.services:lfk/backend into feature/151-selfservice_scans_mails 2021-03-04 16:29:46 +01:00
niggl db58a280b3 Updated readme env section
ref #151
2021-03-04 16:29:44 +01:00
niggl 149f3a83b2 Updated readme env section
ref #151
2021-03-04 16:28:13 +01:00
niggl a5d2a6ecd3 Added locale to pw reset endpoint
ref #151
2021-03-04 16:25:23 +01:00
niggl bb9bad6d90 Now checking for mails being set
ref #151
2021-03-04 16:19:19 +01:00
niggl ada679823c Removed useless functions and updated comments
ref #151
2021-03-04 16:13:36 +01:00
niggl 9a1678acf0 Now using mailer as static funtion
ref #151
2021-03-04 16:11:35 +01:00
niggl 485c247cd3 Removed (now useless) mail controller
ref #151
2021-03-04 16:11:21 +01:00
niggl ddea02db57 Added new mailer settings to config
ref #151
2021-03-04 16:11:05 +01:00
niggl 1551a444ba Added the new mailer code
ref #151
2021-03-04 16:10:43 +01:00
niggl f289afd8bc Updated mail errors
ref #151
2021-03-04 16:10:26 +01:00
niggl a9e06c9055 Promoted axios to dependency
ref #151
2021-03-04 15:56:24 +01:00
niggl c2fdfeed4f Removed mail templates
ref #151
2021-03-04 15:54:47 +01:00
niggl 0342757d92 Removed mail config
ref #151
2021-03-04 15:54:27 +01:00
niggl 5833f4218f Removed nodemailer from backend
ref #151
2021-03-04 15:52:20 +01:00
niggl 0fcc729b56 Removed old mailer code
ref #151
2021-03-04 15:51:05 +01:00
niggl a2c97a11a3 Laptime is now a part of the response
ref #151
2021-03-01 16:59:36 +01:00
niggl aa833736d3 Trackscans now have a laptime that get's calculated on creation
ref #151
2021-03-01 16:58:34 +01:00
niggl 771a205fe6 Added new selfservice scans endpoint
ref #151
2021-03-01 16:49:37 +01:00
niggl 6074ac5b3a Added selfservice scan response class
ref #151
2021-03-01 16:47:54 +01:00
niggl 030b2255d4 Added another resonse type
ref #151
2021-03-01 16:47:05 +01:00
niggl f7f6df41ff Added new selfservice response type
ref #151
2021-03-01 16:36:36 +01:00
niggl be397c8899 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-26 16:50:45 +00:00
niggl dd3c9275d6 Merge pull request 'Alpha Release 0.4.6' (#148) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #148
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-02-26 16:50:03 +00:00
niggl 764b7ffe00 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-26 16:48:33 +00:00
niggl d870b2fd01 Merge pull request 'Fixed wrong body acceptance type' (#150) from bugfix/146-usergroup_update into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #150
2021-02-26 16:48:16 +00:00
niggl aaec09d2ab Fixed wrong body acceptance type
continuous-integration/drone/pr Build is passing
ref #146
2021-02-26 17:32:26 +01:00
niggl bce8811925 📖New license file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-25 19:41:04 +00:00
niggl 3afc207903 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-25 19:39:39 +00:00
niggl fca997beb8 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-02-25 20:39:21 +01:00
niggl 39ebfbf0b6 Pinned package version to avoid dependency conflicts 📌
yeet
2021-02-25 20:39:16 +01:00
niggl 3736b29e54 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-25 19:33:24 +00:00
niggl b4c9369a53 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
2021-02-25 20:33:04 +01:00
niggl 5d6c8c957a Quick bugfix 2021-02-25 20:33:00 +01:00
niggl 09fe47b9aa 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-25 19:30:37 +00:00
niggl b4acd157fc 🚀Bumped version to v0.4.6
continuous-integration/drone/push Build is failing
2021-02-25 20:30:16 +01:00
niggl b1fced7764 📖New license file version [CI SKIP] [skip ci] 2021-02-24 20:00:08 +00:00
niggl c0cafb4d51 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-24 19:59:05 +00:00
niggl 45d61b487e Merge pull request 'New org selfservice endpoint feature/146-more_selfservice_endpoints' (#147) from feature/146-more_selfservice_endpoints into dev
continuous-integration/drone/push Build is failing
Reviewed-on: #147
2021-02-24 19:58:47 +00:00
niggl 28ef139a70 Added tests for the new org selfservice endpoints
continuous-integration/drone/pr Build is passing
ref #146
2021-02-24 18:32:56 +01:00
niggl 656f63dfd5 Added selfservice org info endpoint
ref #146
2021-02-24 18:23:08 +01:00
niggl ba3b5eeefc Added selfservice org response model
ref #146
2021-02-24 18:20:31 +01:00
niggl ba396e0eba Added selfservice team response model
ref #146
2021-02-24 18:18:10 +01:00
niggl 3c11d88557 Added new response types
ref #146
2021-02-24 18:17:30 +01:00
niggl 305fa0078d 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-09 18:25:18 +00:00
niggl a46d14278b Merge pull request 'Alpha release 0.4.5' (#145) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #145
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-02-09 18:24:30 +00:00
niggl 680ae8ebbb 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-09 18:19:21 +00:00
niggl cc869f69ad 🚀Bumped version to v0.4.5
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-02-09 19:18:46 +01:00
niggl b9aac71676 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-09 18:18:22 +00:00
niggl a30a342e00 Merge pull request 'usergroups/permissions endpoint feature/143-usergroup_permissions_endpoint' (#144) from feature/143-usergroup_permissions_endpoint into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #144
2021-02-09 18:18:04 +00:00
niggl bdcfce88cb Now all /usergroups endpoints return ResponseUserGroup
continuous-integration/drone/pr Build is passing
ref #143
2021-02-09 18:27:11 +01:00
niggl dd81f4c7e4 Merge branch 'feature/143-usergroup_permissions_endpoint' of git.odit.services:lfk/backend into feature/143-usergroup_permissions_endpoint
# Conflicts:
#	src/models/responses/ResponseUserGroup.ts
2021-02-09 18:26:33 +01:00
niggl 416f2a1366 The ResponseUserGroup now returns their permisssions as a string array
ref #143
2021-02-09 18:26:22 +01:00
niggl 5e353db206 The ResponseUserGroup now returns their permisssions as a string array
ref #143
2021-02-09 18:26:16 +01:00
niggl 0c9867d706 Implemented /groups/permissions endpoint
ref #143
2021-02-09 18:18:00 +01:00
niggl 8379c3e29c 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-09 16:47:57 +00:00
niggl c4edccace7 Merge pull request 'Alpha release 0.4.4' (#142) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #142
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-02-09 16:47:31 +00:00
niggl 74de6559d7 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-08 16:34:37 +00:00
niggl a6f73c733c 🚀Bumped version to v0.4.4
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-02-08 17:34:03 +01:00
niggl ca3d093e54 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-08 16:33:37 +00:00
niggl 28cfbaa662 Merge pull request 'Expanded runner response feature/140-runner_group_parent' (#141) from feature/140-runner_group_parent into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #141
2021-02-08 16:33:21 +00:00
niggl 90e1ad7db7 Adjusted test for the new response depth
continuous-integration/drone/pr Build is passing
ref #140
2021-02-08 16:51:09 +01:00
niggl 906a1dc9e7 The group/runners endpoints now also deliver the runner's group's parentGroup
ref #140
2021-02-08 16:48:20 +01:00
niggl 5872c6335b Adjusted test for the new response depth
ref #140
2021-02-08 16:46:07 +01:00
niggl 701706c028 Now loading runner's group's parentgroup with every runner controller request
ref #140
2021-02-08 16:40:17 +01:00
niggl 09bbc70f5f 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-07 12:43:57 +00:00
niggl dd9cb6d3ef Merge pull request 'Alpha Release 0.4.3' (#139) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #139
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-02-07 12:43:30 +00:00
niggl 23c732b690 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-07 12:40:54 +00:00
niggl 656d564baa 🚀Bumped version to v0.4.3
continuous-integration/drone/push Build is passing
2021-02-07 13:40:32 +01:00
philipp f3f5cb462e 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-07 12:38:54 +00:00
philipp 9959172f2a Merge pull request 'Bugfix for @lfk/frontend/#43' (#138) from bugfix/118-encode_jwt_in_mail into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #138
2021-02-07 12:38:34 +00:00
niggl 8f0a396dd0 Bugfix for @lfk/frontend/#43
continuous-integration/drone/pr Build is passing
2021-02-07 13:37:01 +01:00
niggl a18d4d3cee 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-02 15:02:34 +00:00
niggl 390b36dfd4 Merge pull request 'Alpha Release 0.4.2' (#137) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #137
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-02-02 15:02:08 +00:00
niggl 3b718f3ce5 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-02-02 14:46:03 +00:00
niggl 321b20b073 🚀Bumped version to v0.4.2
continuous-integration/drone/push Build is passing
2021-02-02 15:45:41 +01:00
niggl f7a0ec7174 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-02 14:45:15 +00:00
niggl 110a84783e Merge pull request 'Imprint&Privacy Links feature/135-imprint_and_privacy' (#136) from feature/135-imprint_and_privacy into dev
continuous-integration/drone/push Build is failing
Reviewed-on: #136
2021-02-02 14:45:00 +00:00
niggl 333e806da4 Added documentation about the new env vars to the readme
continuous-integration/drone/pr Build is passing
ref #135
2021-02-02 15:10:04 +01:00
niggl f4f621973a Added imprint and privacy to the api spec
ref #135
2021-02-02 15:07:33 +01:00
niggl bcad691045 Added new url env vars to config
ref #135
2021-02-02 15:04:09 +01:00
niggl 74791df68b 📖New license file version [CI SKIP] [skip ci] 2021-01-30 17:13:12 +00:00
niggl 8425043099 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 17:12:25 +00:00
niggl 74b982afba fixed license-exporter call
continuous-integration/drone/push Build is passing
2021-01-30 18:12:09 +01:00
niggl 3aefa75412 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 16:00:16 +00:00
niggl 71cab4e836 Merge pull request 'Alpha Release 0.4.1' (#134) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #134
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-30 15:59:51 +00:00
niggl 4e10077901 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-30 15:54:03 +00:00
niggl c32fa93673 🚀Bumped version to v0.4.1
continuous-integration/drone/push Build is passing
2021-01-30 16:53:38 +01:00
niggl 3d1baae0cc Dependency bump🔝 [skip ci] 2021-01-30 16:53:21 +01:00
niggl 94dd7963b7 Deleted useless file [ci skip] 2021-01-30 16:44:39 +01:00
niggl 7ba67b9dca 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 15:40:39 +00:00
niggl 6e5f1bd5ff Merge pull request 'Response object types feature/132-object_types' (#133) from feature/132-object_types into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #133
2021-01-30 15:40:24 +00:00
niggl 60ee6ebc1e Merge branch 'dev' into feature/132-object_types
continuous-integration/drone/pr Build is passing
2021-01-30 15:32:20 +00:00
niggl 02295346da 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 15:27:16 +00:00
niggl c4ea808e06 Merge pull request 'Alpha Release 0.4.0' (#131) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #131
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-30 15:26:48 +00:00
niggl ff7406e71a Cleaned up realations regarding response classes
continuous-integration/drone/pr Build is passing
ref #132
2021-01-30 16:19:42 +01:00
niggl 8dc2810c0c Adjusted tests for the new responseType parameter (part 3)
ref #132
2021-01-30 16:19:20 +01:00
niggl ff8af090e3 Adjusted tests for the new responseType parameter (part 2)
ref #132
2021-01-30 15:39:39 +01:00
niggl bcc15e4286 Adjusted tests for the new responseType parameter (part 1)
ref #132
2021-01-29 18:46:15 +01:00
niggl 2a87819486 Fixed typos and missing types
ref #132
2021-01-29 17:06:57 +01:00
niggl 9d5e486c6d Implemented the interface in all responses
refr #132
2021-01-29 17:06:43 +01:00
niggl e44cc4c4cb Added a Response interface
ref #132
2021-01-29 16:49:19 +01:00
niggl 581ca5ff6c Added Responseobjecttype enum
ref #132
2021-01-29 16:45:23 +01:00
niggl b972395ae8 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-27 16:54:23 +00:00
niggl e5f4f6ee59 🚀Bumped version to v0.4.0
continuous-integration/drone/push Build is passing
2021-01-27 17:53:58 +01:00
niggl fea4857685 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:52:45 +00:00
niggl f9e75d06b8 Merge pull request 'Implemented testmail endpoint feature/124-testmail' (#130) from feature/124-testmail into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #130
2021-01-27 16:52:28 +00:00
niggl 38223b194b Merge branch 'dev' into feature/124-testmail
continuous-integration/drone/pr Build is passing
2021-01-27 17:47:29 +01:00
niggl 09b24aa609 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:47:07 +00:00
niggl 348e6cdec7 Merge pull request 'Email Basics feature/118-emails' (#128) from feature/118-emails into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #128
2021-01-27 16:46:34 +00:00
niggl bd1813a0e8 Merge branch 'dev' into feature/118-emails
continuous-integration/drone/pr Build is passing
2021-01-27 16:45:17 +00:00
niggl e07f258a31 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:44:56 +00:00
niggl 61bbeb0d8f Merge pull request 'Mail+Env documentation feature/123-mail_documentation' (#129) from feature/123-mail_documentation into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #129
2021-01-27 16:44:33 +00:00
niggl 650a55e586 Merge branch 'dev' into feature/123-mail_documentation
continuous-integration/drone/pr Build is passing
2021-01-27 16:44:19 +00:00
niggl 2071c4db33 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:43:56 +00:00
niggl 80e606aa96 Merge branch 'dev' into feature/123-mail_documentation
continuous-integration/drone/pr Build is passing
2021-01-27 16:43:54 +00:00
niggl 20f960ed67 Merge pull request 'Alpha Release 0.3.1' (#127) from dev into main
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is passing
Reviewed-on: #127
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-27 16:43:26 +00:00
niggl e6fe8fcd58 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-27 16:32:04 +00:00
niggl 870fd47c83 Merge pull request 'new advanced endpoints feature/125-team_runner' (#126) from feature/125-team_runner into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #126
2021-01-27 16:31:45 +00:00
niggl 644045db44 Merge branch 'dev' into feature/125-team_runner
continuous-integration/drone/pr Build is passing
2021-01-27 16:31:16 +00:00
niggl 8611fcb849 Merge branch 'dev' into feature/124-testmail
continuous-integration/drone/pr Build is passing
2021-01-27 11:08:37 +00:00
niggl 08e6e59655 Removed the duplicate env copy/create from ci tests
continuous-integration/drone/pr Build is passing
ref #124
2021-01-27 12:06:00 +01:00
niggl ae74b3963f Added test mail sending test
continuous-integration/drone/pr Build is passing
ref #124
2021-01-27 12:03:02 +01:00
niggl 54ed313342 Implemented the test-mail endpoint via a new mailcontroller
ref #124
2021-01-27 11:59:32 +01:00
niggl ad4b903c25 Added a Mail permisssion target
ref #124
2021-01-27 11:53:33 +01:00
niggl 9bd7636a23 Added comments
ref #124
2021-01-27 11:37:46 +01:00
niggl b94179e3ca Added a test mail sending function
ref #124
2021-01-27 11:28:27 +01:00
niggl 827002989e Added test mail templates
ref #124
2021-01-27 11:28:02 +01:00
niggl eeff67c192 Merge branch 'dev' into feature/123-mail_documentation
continuous-integration/drone/pr Build is passing
2021-01-26 19:42:59 +00:00
niggl 583a4bc0dd Changed order
continuous-integration/drone/pr Build is passing
ref #123
2021-01-26 20:41:44 +01:00
niggl 53fcff77d0 Added a hint to ethereal.email
ref #123
2021-01-26 20:41:08 +01:00
niggl 1f0c842d9e Table fix
ref #123
2021-01-26 20:39:38 +01:00
niggl 13ccab5e28 Added documentation for the env vars
ref #123
2021-01-26 20:38:53 +01:00
niggl b5018eb114 Added the basics about mail templates to the readme
ref #123
2021-01-26 20:26:25 +01:00
niggl aedfcfcc83 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-26 19:15:59 +00:00
niggl db0876015b 🚀Bumped version to v0.3.1
continuous-integration/drone/push Build is passing
2021-01-26 20:15:35 +01:00
niggl 69417e93c0 Added get runners by team test
continuous-integration/drone/pr Build is passing
ref #125
2021-01-26 20:13:39 +01:00
niggl f71a22f4dd Added get runners by org test
ref #125
2021-01-26 20:12:14 +01:00
niggl 570c34bed0 Created the organizations/runners endpoint
ref #125
2021-01-26 20:06:54 +01:00
niggl 7be2971a9e Created the runnerTeam/runners endpoint
ref #125
2021-01-26 19:57:35 +01:00
niggl b92f633d68 Now also sending txt mail body
continuous-integration/drone/pr Build is passing
ref #118
2021-01-26 18:45:19 +01:00
niggl d3647e3399 Added a txt variant of the pw-reset mail
ref #118
2021-01-26 18:41:50 +01:00
niggl 389e423850 Cleaned up the replacements
ref #118
2021-01-26 18:41:21 +01:00
niggl 46af786516 Fixed missing app_url protocol
ref #118
2021-01-26 18:31:34 +01:00
niggl b4c117b7dc Fixed wrong file location
ref #118
2021-01-26 18:31:17 +01:00
niggl 5cade25eeb Translated the pw reset mail to english
ref #118
2021-01-26 18:17:52 +01:00
niggl fb77f4d798 Renamed the template
ref #118
2021-01-26 18:09:27 +01:00
niggl c116338cd7 Added pw reset template provided by @philipp
ref #118
2021-01-26 18:09:00 +01:00
niggl 979d36ea91 Password reset now enforces email
ref #118
2021-01-26 18:07:56 +01:00
niggl c43334bf96 The auth tests now use mail to identify the user
ref #118
2021-01-26 18:07:42 +01:00
niggl 71c4caae8b Removed bs console.log
ref #118
2021-01-26 17:55:41 +01:00
niggl 536de2a319 Implemented automatic ci env generation
ref #118
2021-01-26 17:54:25 +01:00
niggl e26744b792 Implementes mail sending on pw reset request
ref #118
2021-01-26 17:35:03 +01:00
niggl d02e9dec56 Removed tests working directly with the old pw-reset response
ref #118
2021-01-26 17:28:20 +01:00
niggl 637975305f Implemented a basic mailer with reset link sending
ref #118
2021-01-26 17:21:18 +01:00
niggl c418603423 Added the first mail error
ref #118
2021-01-26 17:20:55 +01:00
niggl 78d2ac3027 Added nodemailer types
ref #118
2021-01-26 17:20:44 +01:00
niggl 470703c4de Added env vars
ref #118
2021-01-26 17:20:33 +01:00
niggl e260e16d66 Merge branch 'feature/118-emails' of git.odit.services:lfk/backend into feature/118-emails 2021-01-26 16:10:26 +01:00
niggl 6b0155f014 Added a folder for the mail templates
ref #118
2021-01-26 16:10:24 +01:00
niggl 33890b544b Added a folder for the mail templates
ref #118
2021-01-26 16:10:02 +01:00
niggl d7ea928714 Added mail env vars
ref #118
2021-01-26 16:09:11 +01:00
niggl 908ac4f1ce Added nodemailer dependecy
ref #118
2021-01-26 16:06:47 +01:00
niggl cf012c0b7e Added a barebones class for handleing mail stuff
ref #118
2021-01-26 16:05:55 +01:00
niggl 71898d576c 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-24 17:58:01 +00:00
niggl c964591839 Merge pull request 'Alpha Release 0.3.0' (#122) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #122
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-24 17:57:36 +00:00
niggl cc4bf4451c 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-24 17:53:11 +00:00
niggl 7dbbd3780d Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-24 18:52:36 +01:00
niggl 3697783e19 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-24 17:52:09 +00:00
niggl 161feaf364 Merge pull request 'OrganiZation rename feature/117-organization' (#121) from feature/117-organization into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #121
2021-01-24 17:51:50 +00:00
niggl 75e2a44c9c 🚀Bumped version to v0.3.0 2021-01-24 18:48:06 +01:00
niggl cd7e9b86b4 Renamedpermisssions from *Organisation* to *Organization*📝
continuous-integration/drone/pr Build is passing
ref #117
2021-01-24 18:43:29 +01:00
niggl c6c643ecf1 Renamed files and classed from *Organisation* to *Organization*📝
ref #117
2021-01-24 18:40:46 +01:00
niggl ef15d0d576 Changed organisation* to organization* in descriptions, comments and endoints ✏
ref #117
2021-01-24 18:34:15 +01:00
niggl 5660aecb50 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-22 14:13:45 +00:00
niggl 6a66dd803b Merge pull request 'Self service registration feature/112-selfservice_registration' (#120) from feature/112-selfservice_registration into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #120
2021-01-22 14:13:25 +00:00
niggl b42f0722d7 Merge branch 'dev' into feature/112-selfservice_registration
continuous-integration/drone/pr Build is passing
2021-01-21 20:02:23 +01:00
niggl 45c8bb83be Fixed tests testing for a old responseclass
ref #112
2021-01-21 20:01:44 +01:00
niggl 6469e3bc97 Fixed wrong error getting thrown
ref #112
2021-01-21 19:51:40 +01:00
niggl 10f98e9c99 Bugfix: turned old entity in response to responseclass
ref #112
2021-01-21 19:49:11 +01:00
niggl e5b6f650b2 Added registration invalid company tests
ref #112
2021-01-21 19:48:45 +01:00
niggl 3b2ed3f0f2 Resolved missing relation
ref #112
2021-01-21 19:46:46 +01:00
niggl 20e102ec5c Added registration valid company tests
ref #112
2021-01-21 19:46:32 +01:00
niggl 5a003945ac Updated response schema error to a more fitting one
ref #112
2021-01-21 19:34:27 +01:00
niggl 29aeb046de Added registration invalid company tests
ref #112
2021-01-21 19:34:11 +01:00
niggl 72941da1cb Added registration valid citizentests
ref #112
2021-01-21 19:33:55 +01:00
niggl 81d2197a3e Added registration invalid citizen tests
ref #112
2021-01-21 19:22:26 +01:00
niggl 9dd9304a71 Citizen registration now returns tokens
ref #112
2021-01-21 19:17:40 +01:00
niggl 0c87906cc3 Added selfservice get positive test
ref #112
2021-01-21 19:17:25 +01:00
niggl 1227408407 Fixed fluctuating test bahaviour
ref #112
2021-01-21 19:10:59 +01:00
niggl f8d7544517 Marked param as optional (default: false)
ref #112
2021-01-21 19:08:43 +01:00
niggl a9843ed459 Updates old tests to the new ss-ktokens
ref #112
2021-01-21 19:08:21 +01:00
niggl 46f9503543 Fixed typo
ref #112
2021-01-21 18:45:15 +01:00
niggl c5d0646c42 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-21 17:32:17 +00:00
niggl b441658570 Merge pull request 'Alpha Release 0.2.1' (#119) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #119
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-21 17:31:49 +00:00
niggl e95c457e44 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-21 17:15:47 +00:00
niggl 6de9d547b7 🚀Bumped version to v0.2.1
continuous-integration/drone/push Build is passing
2021-01-21 18:15:24 +01:00
niggl 3a93c9c078 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-21 17:14:56 +00:00
niggl 36d01a0a89 Merge pull request 'Runner scans endpoint feature/113-runner_scans' (#116) from feature/113-runner_scans into dev
continuous-integration/drone/push Build is failing
Reviewed-on: #116
2021-01-21 17:14:41 +00:00
niggl 6434b4dfce Added check for empty token for runner self-service get
ref #112
2021-01-21 18:12:53 +01:00
niggl e964a8ed44 Added self-service get invalid tests
ref #112
2021-01-21 18:12:29 +01:00
niggl c39a59e54e Implemented runner selfservice token generation
ref #112
2021-01-21 18:03:48 +01:00
niggl 34c852b12a Specified uft-8 format for string
ref #112
2021-01-21 17:54:11 +01:00
niggl 7b00b19fce MAde uuid column unique
ref #112
2021-01-21 17:52:03 +01:00
niggl ad446500f9 Implemented registration key generation
ref #112
2021-01-21 17:48:13 +01:00
niggl d490247d1e Implemented a registration key for organisations
ref #112
2021-01-21 17:30:43 +01:00
niggl dee36395a6 Citizen runners now have to provide an email address for verification
ref #112
2021-01-21 17:19:04 +01:00
niggl 6df195b6ec Created a citizenrunner selfservice create action
ref #112
2021-01-21 17:18:25 +01:00
niggl 946efef252 Updated Method of removeing the team of citizen runners
ref #112
2021-01-21 17:01:56 +01:00
niggl 73b1114883 Added openapi description
ref #112
2021-01-21 16:48:53 +01:00
niggl 1b5465bea8 Implemented the citizen runner self-registration endpoint
ref #112
2021-01-21 16:47:13 +01:00
niggl 5288c701c1 Implemented the basics for the runner selfservice registration endpoint
ref #112
2021-01-21 16:43:04 +01:00
niggl 10af1ba341 Implemented a runner selfservice registration creation action
ref #112
2021-01-21 16:40:47 +01:00
niggl 26dff4f418 Added get tests for the /runner/scans endpoint
continuous-integration/drone/pr Build is passing
ref #113
2021-01-21 16:07:11 +01:00
niggl b5f3dec93b Added a "onlyValid" query param
ref #113
2021-01-21 15:57:56 +01:00
niggl a82fc0fb9e Added a /runners/id/scans endpoint
ref #113
2021-01-21 15:55:29 +01:00
niggl e2ec0a3b64 Readme reorganisation [skip ci] 2021-01-21 15:43:11 +01:00
niggl f4668b6e81 Added sqlite as to env.sample db of choice [skip ci] 2021-01-21 15:27:11 +01:00
niggl d5281348b6 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-20 19:14:43 +00:00
niggl 1717df113e Merge pull request 'Runner selfservice info endpoint feature/111-runner_selfservic_info' (#115) from feature/111-runner_selfservic_info into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #115
2021-01-20 19:14:19 +00:00
niggl 0355bdbbab Merge branch 'dev' into feature/111-runner_selfservic_info
continuous-integration/drone/pr Build is passing
2021-01-20 19:13:18 +00:00
philipp 02677de5c0 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-20 19:13:07 +00:00
philipp 886c1092d6 Merge pull request 'Implemented more seeding feature/110-seeding' (#114) from feature/110-seeding into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #114
2021-01-20 19:12:49 +00:00
niggl 191569792c Updated the openapi description
continuous-integration/drone/pr Build is passing
ref #111
2021-01-20 20:07:16 +01:00
niggl da1fe34249 Implemented the get part of the runner selfservice (no jwts are availdable yet (tm)
ref #111
2021-01-20 20:05:07 +01:00
niggl 4ee807973e Fixed wrong amount calculation
ref #111
2021-01-20 20:02:30 +01:00
niggl c5f7cb2c68 Beautified import
ref #111
2021-01-20 19:44:24 +01:00
niggl 88a7089289 Created a donation runner response class for the runner selfservice
ref #111
2021-01-20 19:43:53 +01:00
niggl b89f7ac1b4 Created a donation respoinse class for the runner selfservice
ref #111
2021-01-20 19:43:20 +01:00
niggl 8079769881 Implemented a method for getting the runner object from a jwt
ref #110
2021-01-20 19:20:08 +01:00
niggl 2274b476d6 Added barebones controller for the runner info selfservice
ref #111
2021-01-20 19:05:59 +01:00
niggl e12aedd1aa Fixed the bool converter for null values
continuous-integration/drone/pr Build is passing
ref #110
2021-01-20 18:28:41 +01:00
niggl 434aaf6136 Merge branch 'dev' into feature/110-seeding
continuous-integration/drone/pr Build is failing
2021-01-20 17:09:14 +00:00
niggl d8b6669d12 📖New license file version [CI SKIP] [skip ci] 2021-01-20 17:07:14 +00:00
niggl dd3d93edc7 Merge pull request 'Alpha Release 0.2.0' (#109) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #109
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-20 17:05:12 +00:00
niggl 7bc603028d The data seeding now only get's triggered on the first time thx to using the key-value
continuous-integration/drone/pr Build is failing
ref #110
2021-01-20 18:02:08 +01:00
niggl c18012f65a Added bool conversion for testdata seeding env var
ref #110
2021-01-20 17:59:33 +01:00
niggl b15967ff31 Added key-value like db table for config flags
ref #110
2021-01-20 17:58:28 +01:00
niggl 2db6510a8a Added a citizen org seeder
ref #110
2021-01-20 17:58:11 +01:00
niggl 1837336865 Now creating a test contact
ref #110
2021-01-20 17:38:34 +01:00
niggl eab0e634a2 Now also seeding runners to the test org
ref #110
2021-01-20 17:34:53 +01:00
niggl 8870ebdb5e SEED_TEST_DATA is now false by default
ref #110
2021-01-20 17:33:31 +01:00
niggl 9df9d9ae80 Added a seeder for runner test data
ref #110
2021-01-20 17:26:04 +01:00
niggl 67ba489fe2 Added a config option for test data seeding
ref #110
2021-01-20 17:25:46 +01:00
niggl da9a359251 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-19 19:10:15 +00:00
niggl 0661729e5f Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is passing
2021-01-19 20:09:36 +01:00
niggl ddafd90d3e 🚀Bumped version to v0.2.0 2021-01-19 20:09:30 +01:00
niggl 8960aa5545 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-19 19:09:11 +00:00
niggl a0c2b5ade8 Merge pull request 'Implemented group contacts feature/104-contacts' (#108) from feature/104-contacts into dev
continuous-integration/drone/push Build is failing
Reviewed-on: #108
2021-01-19 19:08:53 +00:00
niggl a1acd3519f Adjusted env sample
continuous-integration/drone/pr Build is passing
ref #104 ref #105
2021-01-19 19:33:11 +01:00
niggl c3d008ec0f Updated contact update tests
continuous-integration/drone/pr Build is passing
ref #104
2021-01-19 19:32:39 +01:00
niggl 8ae53f1c49 Updated contact delete tests
ref #104
2021-01-19 19:12:53 +01:00
niggl 179c2a5157 Fixed contact cascading
ref #104
2021-01-19 19:04:46 +01:00
niggl dd7e5dae36 Added contact delete tests
ref #104
2021-01-19 19:04:09 +01:00
niggl e165f01930 Added contact add valid tests
ref #104
2021-01-19 18:48:37 +01:00
niggl 940d62cde4 Added contact add invalid tests
ref #104
2021-01-19 18:14:09 +01:00
niggl b002cf2df1 Added contact get tests
ref #104
2021-01-19 18:13:39 +01:00
niggl 56c73c2555 Added openapi description about non-deletion
ref #104
2021-01-19 18:03:29 +01:00
niggl 28fb9834e1 Implemented contact updateing
ref #104
2021-01-19 18:01:37 +01:00
niggl 6b4b16c13b Added missing id property 2021-01-19 18:00:45 +01:00
niggl d743f7ee12 Renamed controller to better fit the overall nameing scheme
ref #104
2021-01-19 17:58:03 +01:00
niggl a4e8311cbd Updated comments
ref #104
2021-01-19 17:57:15 +01:00
niggl c172aa8bf8 Added a contact update class
ref #104
2021-01-19 17:55:56 +01:00
niggl d1926fe372 Merge branch 'feature/104-contacts' of git.odit.services:lfk/backend into feature/104-contacts 2021-01-19 17:53:02 +01:00
niggl 2b658ac381 Fixed column not getting resolved
ref #104
2021-01-19 17:52:59 +01:00
niggl 321d291b4b Fixed column not getting resolved 2021-01-19 17:52:51 +01:00
niggl 2eb26e4e38 Fixed push undefined eror
ref #104
2021-01-19 17:41:00 +01:00
niggl 3b06d1a6ef Implemented contact group setting on creation
ref #104
2021-01-19 17:29:52 +01:00
niggl de824375d3 Fixed key null constraint
ref #104
2021-01-19 17:27:43 +01:00
niggl 11af9c02d9 Implemented contact posting
ref #104
2021-01-19 17:14:05 +01:00
niggl 09e429fc67 Added address to contact response
ref #104
2021-01-19 17:13:46 +01:00
niggl 703b4f89a6 Merge branch 'dev' into feature/104-contacts 2021-01-19 16:44:34 +01:00
niggl 32e054eb84 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-19 15:37:52 +00:00
niggl 5e368552ea Merge pull request 'Fully implemented addresses feature/105-addresses' (#107) from feature/105-addresses into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #107
2021-01-19 15:37:35 +00:00
niggl 0379786cbd Implemented contact deletion
ref #104
2021-01-19 16:09:23 +01:00
niggl a9a5eb6735 Updated the contact errors
ref #104
2021-01-19 16:06:42 +01:00
niggl ab70f7e498 Implemented the get endpoints
ref #104
2021-01-19 16:05:35 +01:00
niggl 1407fe36f3 Added a contact response class
ref #104
2021-01-19 16:02:13 +01:00
niggl d12801e34d Added contact permission target
ref #104
2021-01-19 15:56:55 +01:00
niggl 3e7190e279 Added barebones contact controller from donor-controller
ref #104
2021-01-19 15:56:03 +01:00
niggl 41423feffe Merge branch 'dev' into feature/105-addresses
continuous-integration/drone/pr Build is passing
2021-01-19 14:51:07 +00:00
niggl 30b585c0c1 Set country code for the ci env to DE
continuous-integration/drone/pr Build is passing
ref #105
2021-01-19 15:49:35 +01:00
niggl a3c93f0d39 Cleaned up var names
continuous-integration/drone/pr Build is failing
ref #105
2021-01-19 15:48:06 +01:00
niggl f53894b16a 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-16 20:33:37 +00:00
niggl 7533c349ef Merge pull request 'Alpha Release 0.1.1 - Hotfix release' (#106) from dev into main
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #106
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-16 20:32:39 +00:00
niggl 91569ced40 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-16 20:30:47 +00:00
niggl f9ae778b21 Merge branch 'main' into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-16 20:30:28 +00:00
niggl 427dfaafab Added address update ivalid tests
continuous-integration/drone/pr Build is failing
ref #105
2021-01-16 21:26:45 +01:00
niggl ae589aeb54 Merge branch 'dev' into feature/105-addresses
# Conflicts:
#	src/errors/AddressErrors.ts
#	src/models/actions/create/CreateAddress.ts
#	src/models/actions/create/CreateDonor.ts
#	src/models/actions/create/CreateGroupContact.ts
#	src/models/actions/create/CreateParticipant.ts
#	src/models/actions/create/CreateRunner.ts
#	src/models/actions/create/CreateRunnerOrganisation.ts
#	src/models/actions/update/UpdateDonor.ts
#	src/models/actions/update/UpdateRunner.ts
#	src/models/actions/update/UpdateRunnerOrganisation.ts
#	src/models/entities/Address.ts
#	src/models/entities/IAddressUser.ts
#	src/models/entities/RunnerOrganisation.ts
#	src/models/responses/ResponseParticipant.ts
#	src/tests/donors/donor_add.spec.ts
#	src/tests/donors/donor_update.spec.ts
#	src/tests/runnerOrgs/org_add.spec.ts
#	src/tests/runnerOrgs/org_delete.spec.ts
#	src/tests/runnerOrgs/org_update.spec.ts
#	src/tests/runnerTeams/team_update.spec.ts
#	src/tests/runners/runner_update.spec.ts
2021-01-16 21:15:02 +01:00
niggl 1b9d2969eb 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-16 20:06:31 +00:00
niggl daffbcde72 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build was killed
# Conflicts:
#	CHANGELOG.md
2021-01-16 21:06:12 +01:00
niggl 9445c6f21e 🚀Bumped version to v0.1.1 2021-01-16 21:05:43 +01:00
niggl 6febb99499 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-16 20:00:06 +00:00
niggl 6e6979cfe3 Hotfix: Missing relation bug
continuous-integration/drone/push Build is passing
2021-01-16 20:59:48 +01:00
niggl 230cdb0e37 Added address update valid tests
ref #105
2021-01-16 20:37:48 +01:00
niggl ce450e9b6d Merge branch 'dev' into feature/105-addresses 2021-01-16 20:33:28 +01:00
niggl de36a24191 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-16 19:32:57 +00:00
niggl b167ba07f7 Hotfix: Missing relation bug
continuous-integration/drone/push Build is passing
2021-01-16 20:32:40 +01:00
niggl 4d40225a44 Added first address update tests
ref #105
2021-01-16 20:26:58 +01:00
niggl 57b9c2babc Implemented adress deletion (through reset)
ref #105
2021-01-16 20:19:09 +01:00
niggl 9dc9ce37d8 Implemented deep address validation
ref #105
2021-01-16 20:12:17 +01:00
niggl f245840cde Implemented postal code validation for the validaton function
ref #105
2021-01-16 18:59:06 +01:00
niggl 4824547dde Fixed donor address check
ref #105
2021-01-16 18:52:57 +01:00
niggl 8dbee32eee Test's now accept the new address format
ref #105
2021-01-16 18:34:53 +01:00
niggl ae7c5ff0c3 Added address validity check
ref #105
2021-01-16 18:28:19 +01:00
niggl 2a465f88c5 Removed old create address class
ref #105
2021-01-16 17:03:05 +01:00
niggl 58ae9b589a Removed the address errors
ref #105
2021-01-16 16:58:55 +01:00
niggl 8bc01d3f24 Updated comments
ref #105
2021-01-16 16:57:58 +01:00
niggl d0df5dd641 Switched the update classes over to the new address implementation
ref #105
2021-01-16 16:56:46 +01:00
niggl 2cd15d25e9 Switched the create classes over to the new address implementation
ref #105
2021-01-16 16:55:30 +01:00
niggl dafac06bc8 Updated the responseclasses to use the new address implementation
ref #105
2021-01-16 16:53:18 +01:00
niggl e2651728c5 Removed the IAddressUser Interface entity
ref #105 - It was only needed b/c addresses were implemented as their own class
2021-01-16 16:50:04 +01:00
niggl 673dea2e57 Removed (now useless) relations
ref #105
2021-01-16 16:48:20 +01:00
niggl 7fbe649dc9 Switched Address to embedded entity
ref #105
2021-01-16 16:45:49 +01:00
niggl 3766899c83 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-15 21:57:40 +00:00
niggl a6c7d54fe7 Merge pull request 'User self-management feature/100-me_endpoints' (#103) from feature/100-me_endpoints into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #103
2021-01-15 21:57:21 +00:00
niggl 79bc04bec1 Merge branch 'dev' into feature/100-me_endpoints
continuous-integration/drone/pr Build is passing
2021-01-15 21:56:57 +00:00
niggl f9834b5f4d Moved the me endpoints to /users/me
continuous-integration/drone/pr Build is passing
ref #100
2021-01-15 22:45:45 +01:00
niggl fc7b8f4c16 Updated descriptions and responses
continuous-integration/drone/pr Build is passing
ref #100
2021-01-15 22:43:22 +01:00
niggl 4f6e81677c Implemented getting own permissions
ref #100
2021-01-15 22:35:50 +01:00
niggl 6b7ecd3044 User deletion now requires confirmation
ref #100
2021-01-15 22:35:23 +01:00
niggl 8ef5f90abd Implemented the /me controller that allows a user to get and update themselves
ref #100
2021-01-15 22:28:18 +01:00
niggl a334adffc6 Moved optional param to being optional
ref #100
2021-01-15 22:27:44 +01:00
niggl f1db883609 Implemented a baisc user checker/getter
ref #100
2021-01-15 22:16:28 +01:00
niggl e586a11e2a Created barebones file for the userchecker
ref #100
2021-01-15 21:57:39 +01:00
niggl 50b893f537 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-15 20:53:36 +00:00
niggl 02efb9a8e5 automaticly merge main into dev after building a latest image
continuous-integration/drone/push Build is passing
2021-01-15 21:53:20 +01:00
niggl 38b9a772cd Merge pull request 'First feature version 0.1.0' (#102) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #102
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-15 19:31:40 +00:00
niggl 618430433d 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-15 19:18:43 +00:00
niggl 84cd398c09 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-15 20:18:19 +01:00
niggl 385a9bba73 Fixed broken pkg stuff
ref #102
2021-01-15 20:18:14 +01:00
niggl 8218a452bd 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-15 18:18:39 +00:00
niggl a77e2eb3ad Fixed country code type issue
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
https://ci.odit.services/lfk/backend/252/1/2 ref #102
2021-01-15 19:18:23 +01:00
niggl d1a0bed00e 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is failing
2021-01-15 18:02:24 +00:00
niggl 66d4770858 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-01-15 19:01:32 +01:00
niggl 80c5f9b84d 🚀Bumped version to v0.1.0 2021-01-15 19:01:05 +01:00
niggl 79f46cb745 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is failing
2021-01-15 18:01:01 +00:00
niggl de32a9862d 👊 Bumped dependency
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
ref #102
2021-01-15 19:00:45 +01:00
niggl 0e119e4834 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-15 17:58:01 +00:00
niggl 29c8e00477 Merge pull request 'Switched to accepting ids (numbers/number arrays) feature/90-accept_objects' (#101) from feature/90-accept_objects into dev
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
Reviewed-on: #101
2021-01-15 17:57:45 +00:00
niggl dc6ad9cdd3 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-15 17:53:56 +00:00
niggl dcd754dac8 Merge branch 'dev' into feature/90-accept_objects
continuous-integration/drone/pr Build is passing
2021-01-15 18:53:52 +01:00
niggl d88fb18319 Switched tests over to the new id-only schema
continuous-integration/drone/pr Build is passing
ref #90
2021-01-15 18:50:35 +01:00
niggl 420e9c4662 Updated faulty getter function
ref #90
2021-01-15 18:39:30 +01:00
niggl 98d6a1cc64 Fixed old reference
ref #90
2021-01-15 18:39:04 +01:00
niggl 09ad081b37 Updated faulty getter function
ref #90
2021-01-15 18:36:57 +01:00
niggl aa0fd9cafd Refactoring: switched update user groups from objects to ids
ref #90
2021-01-15 18:35:21 +01:00
niggl bae8290273 Switched to full update from partial and resolved relation
ref #90
2021-01-15 18:33:53 +01:00
niggl 1b799a6973 Clarified comments
ref #90
2021-01-15 18:32:41 +01:00
niggl ed3b55a1e2 Refactoring: switched update team parent from objects to ids
ref #90
2021-01-15 18:31:23 +01:00
niggl 97c01ce81a Refactoring: switched update org address from objects to ids
ref #90
2021-01-15 18:30:20 +01:00
niggl e96637219f Refactoring: switched update runner group from objects to ids
ref #90
2021-01-15 18:29:30 +01:00
niggl 17244b0006 Clarified comments
ref #90
2021-01-15 18:28:24 +01:00
niggl 67a02f06da Merge branch 'feature/90-accept_objects' of git.odit.services:lfk/backend into feature/90-accept_objects
# Conflicts:
#	src/models/actions/update/UpdatePermission.ts
2021-01-15 18:27:32 +01:00
niggl 6b6f345618 Refactoring: switched from objects to ids
ref #90
2021-01-15 18:27:21 +01:00
niggl 2ac9d3e977 Refactoring: switched from objects to ids
ref #90
2021-01-15 18:26:39 +01:00
niggl 93692ec255 Clarified comments
ref #90
2021-01-15 18:25:48 +01:00
niggl 99852f591e Clarified comments
ref #90
2021-01-15 18:23:30 +01:00
niggl b89525746d Clarified comments
ref #90
2021-01-15 18:22:26 +01:00
niggl c05834f2a1 Removed useless parts from functions and updated comments
ref #90
2021-01-15 18:20:56 +01:00
niggl 9bbfb4763d Clarified comments
ref #90
2021-01-15 18:19:34 +01:00
niggl 22e6070e53 Removed useless part from function and updated comments
ref #90
2021-01-15 18:19:26 +01:00
niggl ba218c85e0 Made addresses optional gain 2021-01-15 18:18:26 +01:00
niggl 644d2b06ac Removed useless part from function and updated comments
ref #90
2021-01-15 18:13:53 +01:00
niggl 8d4c8a4553 Removed useless part from function
ref #90
2021-01-15 18:13:10 +01:00
niggl 077174a9a2 Clarified comments
ref #90
2021-01-15 18:12:55 +01:00
niggl ce31b95fb7 Removed todo
#90
2021-01-15 18:10:55 +01:00
niggl 881eedbf3a Merge pull request 'Alpha Release 0.0.12' (#98) from dev into main
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #98
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-13 19:33:07 +00:00
niggl 09cb6f7b2b 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build is passing
2021-01-13 18:25:55 +00:00
niggl bd091d5cb9 🚀Bumped version to v0.0.12
continuous-integration/drone/push Build is passing
2021-01-13 19:25:28 +01:00
niggl 8cb67a8d20 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-13 18:22:48 +00:00
niggl 290bb29e64 Disabled auto clone
continuous-integration/drone/push Build is passing
ref #63
2021-01-13 19:22:19 +01:00
niggl d0769a5e37 Added secondary full clone for tags
continuous-integration/drone/push Build is failing
ref #63
2021-01-13 19:20:42 +01:00
niggl c5b28df2ae Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is passing
# Conflicts:
#	.drone.yml
2021-01-13 19:18:39 +01:00
niggl c108fa509f Updated step order
ref #63
2021-01-13 19:18:31 +01:00
niggl 1e5e9801be Updated step order
ref #63
2021-01-13 19:18:08 +01:00
niggl 09b16c980b 📖New license file version [CI SKIP] [skip ci] 2021-01-13 18:15:22 +00:00
niggl 4c26fc808e Fixed spellings
continuous-integration/drone/push Build is passing
ref #63
2021-01-13 19:14:16 +01:00
niggl 525b11b346 Revert "🚀Bumped version to v0.0.12."
continuous-integration/drone/push Build encountered an error
This reverts commit 86679b498b.
2021-01-13 19:13:04 +01:00
niggl 86679b498b 🚀Bumped version to v0.0.12. 2021-01-13 19:12:50 +01:00
niggl 46df8b0528 Updated the release machanics
ref #63
2021-01-13 19:12:43 +01:00
niggl 1a4f896a8a Merge branch 'dev' of git.odit.services:lfk/backend into dev 2021-01-13 19:07:57 +01:00
niggl aaaa15a0ef Moved changelog generation to dev build for now
ref #63
2021-01-13 19:07:50 +01:00
niggl de65b1c699 🧾New changelog file version [CI SKIP] [skip ci]
continuous-integration/drone/pr Build was killed
2021-01-13 17:58:33 +00:00
niggl f9437065ee Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-13 18:57:17 +01:00
niggl b495cadae9 Added new ci skipping flags
ref #63
2021-01-13 18:57:11 +01:00
niggl 47995b77f7 🧾New changelog file version [CI SKIP]
continuous-integration/drone/pr Build was killed
2021-01-13 17:55:17 +00:00
niggl bc24ec5272 🧾New changelog file version [CI SKIP]
continuous-integration/drone/pr Build is passing
2021-01-13 17:54:26 +00:00
niggl 2947c41a72 🧾New changelog file version [CI SKIP]
continuous-integration/drone/pr Build is passing
2021-01-13 17:54:00 +00:00
niggl ef53035f70 Reenabled dev build
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
ref #63
2021-01-13 18:53:38 +01:00
niggl 290afc3f8f Disabled verification skip
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
ref #63
2021-01-13 18:50:50 +01:00
niggl d6e89b0880 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2021-01-13 18:47:03 +01:00
niggl 2b72552b1f tmp: skip verification 2021-01-13 18:47:00 +01:00
niggl df69418855 tmp: skip verification
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2021-01-13 18:45:04 +01:00
niggl 472e402521 disabled dev build temporary
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
ref #63
2021-01-13 18:42:33 +01:00
niggl a3f282667c Merge branch 'dev' of git.odit.services:lfk/backend into dev
# Conflicts:
#	.drone.yml
2021-01-13 18:42:01 +01:00
niggl b86263d972 Disabled custom clone
ref #63
2021-01-13 18:41:02 +01:00
niggl f278320b93 Disabled custom clone
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
ref #63
2021-01-13 18:38:05 +01:00
niggl 6345666ae6 Added new pipeline to automagicly generate changelogs on pr to main
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
ref #63
2021-01-13 18:35:12 +01:00
niggl 7b5ebab453 Merge pull request 'New user features feature/93-user_endpoints' (#95) from feature/93-user_endpoints into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #95
2021-01-13 17:30:25 +00:00
niggl d4d713b12d Merge branch 'dev' into feature/93-user_endpoints
continuous-integration/drone/pr Build is passing
2021-01-13 17:21:22 +00:00
niggl ab3af54e15 Merge pull request 'Donation API Endpoint feature/66-donation_api' (#94) from feature/66-donation_api into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #94
2021-01-13 17:20:08 +00:00
niggl b01e1eb8a1 Added a new endpoint that returns a users permissions as objects sorted into two arrays
continuous-integration/drone/pr Build is passing
ref #93
2021-01-13 18:19:59 +01:00
niggl 0724932152 Updated some openapi descriptions
continuous-integration/drone/pr Build is passing
ref #94
2021-01-13 18:01:53 +01:00
niggl cd7b15aadf First part of resolving user inherited permissions
ref #93
2021-01-13 17:57:42 +01:00
niggl 37fc167002 Added '@' as a illegal character for usernames
ref #93
2021-01-13 17:51:42 +01:00
niggl 9feeb302e8 Switched emails to being mandetory for users
ref #93
2021-01-13 17:44:22 +01:00
niggl bba35d189e Added donor donation amount to the donor response
continuous-integration/drone/pr Build is failing
ref #66
2021-01-13 17:32:10 +01:00
niggl cd5e4bbd60 Added donation update validtests
ref #66
2021-01-13 17:19:57 +01:00
niggl a513bf13ca Added donation update invalid tests
ref #66
2021-01-12 20:43:07 +01:00
niggl e3e570e664 Added donation add validtests
ref #66
2021-01-12 20:15:51 +01:00
niggl badff85e28 Fixed typos
ref #66
2021-01-12 20:14:23 +01:00
niggl 4a0f75044f Added donation add invalid tests
ref #66
2021-01-12 20:09:00 +01:00
niggl b729a7cead Added cascading runner deletion tests
ref #66
2021-01-12 20:01:56 +01:00
niggl 4375ca92d3 Added cascading donor deletion tests
ref #66
2021-01-12 20:00:02 +01:00
niggl 71537b283f Added donation delete tests
ref #66
2021-01-12 19:53:03 +01:00
niggl 63506dac1c Added donation get tests
ref #66
2021-01-12 19:44:15 +01:00
niggl e716fae1c5 Implmented cascading donation deletion for runners and donors
ref #66
2021-01-12 19:33:54 +01:00
niggl f7370bc802 Implemented distance donation updateing
ref #66
2021-01-12 19:06:26 +01:00
niggl 72c3fc78b3 Added the basics for distance donation updateing
ref #66
2021-01-12 19:03:33 +01:00
niggl 110387dbd3 Merge branch 'feature/66-donation_api' of git.odit.services:lfk/backend into feature/66-donation_api
# Conflicts:
#	src/controllers/DonationController.ts
2021-01-12 19:01:14 +01:00
niggl 2820f151e8 Implemented fixed donation updateing
ref #66
2021-01-12 19:01:03 +01:00
niggl 9517df5082 Implemented fixed donation updateing
ref #66
2021-01-12 19:00:35 +01:00
niggl 56cedf0144 Fixed typo
ref #66
2021-01-12 18:55:20 +01:00
niggl bbaee7cd4d Added the basics for fixed donation updateing
ref #66
2021-01-12 18:53:59 +01:00
niggl 8ee2bdf488 Implemented distance donation creation
ref #66
2021-01-12 18:50:55 +01:00
niggl 97ecc83fe4 Implemented fixed donation creation
ref #66
2021-01-12 18:50:47 +01:00
niggl 57f62a6087 Implemented donation deletion
ref #66
2021-01-12 18:46:02 +01:00
niggl 2e760ff461 Implemented the donation creation action models
ref #66
2021-01-12 18:39:14 +01:00
niggl 0df26cbd54 Implemented donation getting
ref #66
2021-01-12 18:29:55 +01:00
niggl 5f1ab4a2f3 Added donation errors
ref #66
2021-01-12 18:26:55 +01:00
niggl e1ff8c03e1 Added donation permission target
ref #66
2021-01-12 18:21:52 +01:00
niggl 55f72c35a6 Implemented the distance donation response
ref #66
2021-01-12 18:20:36 +01:00
niggl 6c53701a59 Implemented the donation response
ref #66
2021-01-12 18:16:09 +01:00
niggl 02bb634257 Implemented a response donation interface
ref #66
2021-01-12 18:07:41 +01:00
niggl 5581c03f77 Added barebones donation controller
ref #66
2021-01-12 18:01:03 +01:00
niggl cf788fe07b Merge pull request 'Fixed backend version related bugs' (#92) from bugfix/91-backend_version into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #92
closes #91
2021-01-12 16:46:37 +00:00
niggl 4bf425e1ca Merge branch 'dev' into bugfix/91-backend_version
continuous-integration/drone/pr Build is passing
2021-01-12 16:46:08 +00:00
niggl a2f4fd5d9b Introduces a very basic version getting endpoint
continuous-integration/drone/pr Build is passing
ref #91
2021-01-12 17:41:42 +01:00
niggl 295a1524d8 Fixed the version getting process
ref #91
2021-01-12 17:39:40 +01:00
niggl 234154255c Merge pull request 'Bugfix: resolved missing relation' (#89) from bugfix/88-user_update into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #89
closes #88
2021-01-12 16:34:36 +00:00
niggl 7b087840ec Bugfix: resolved missing relation
continuous-integration/drone/pr Build is passing
ref #88
2021-01-12 16:53:39 +01:00
niggl 16b594ebdd Merge branch 'main' into dev
continuous-integration/drone/push Build is passing
2021-01-10 18:28:09 +01:00
niggl 67b3101fd1 Updated some trone pipeline names and messages
continuous-integration/drone/push Build is failing
2021-01-10 18:26:11 +01:00
niggl b3ce56c605 Merge pull request 'Alpha Release 0.0.11' (#87) from dev into main
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #87
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-10 17:16:09 +00:00
niggl 28cefa792c Version bump
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-10 18:13:08 +01:00
niggl 0803abc168 Merge pull request 'General cleanup and optimisation feature/76-cleanup' (#86) from feature/76-cleanup into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #86
2021-01-10 17:11:31 +00:00
niggl 02ae883fa4 Removed everything comit related from the release-it config
continuous-integration/drone/pr Build is passing
ref #76
2021-01-10 18:09:57 +01:00
niggl be4050768e Moded group updateing to a updateusergroup action model
continuous-integration/drone/pr Build is passing
ref #76
2021-01-10 18:01:22 +01:00
niggl dc6ec23cb9 Implmented basic release mgnt
ref #76
2021-01-10 17:47:31 +01:00
niggl 1bb98c13d1 Dependency bump
ref #76
2021-01-10 17:29:30 +01:00
niggl bca979bab5 Unified remove parameters
ref #76
2021-01-10 17:16:42 +01:00
niggl e4fafd764c Cleaner implementation of the api version getter
ref #76
2021-01-10 17:14:42 +01:00
niggl 172159414b Unified the openapi generation
ref #76
2021-01-10 17:10:25 +01:00
niggl 9355138a8c App now automagicly displays the current package version as the openapi version
ref #76
2021-01-10 16:59:39 +01:00
niggl 343cd8b772 Merge branch 'feature/76-cleanup' of git.odit.services:lfk/backend into feature/76-cleanup 2021-01-10 16:57:43 +01:00
niggl 01e0d5b94d Renamed the auth response call to ResponseAuth
ref #76
2021-01-10 16:57:40 +01:00
niggl ac00667465 Renamed the auth response call to ResponseAuth
ref #76
2021-01-10 16:54:19 +01:00
niggl 3deae2bfeb Moved all update() and toEntity action model functions to async
ref #76
2021-01-10 16:53:59 +01:00
niggl 3f7b0f6563 Renamed the update>Entity Name>() functiuons to update()
ref #76
2021-01-10 16:35:52 +01:00
niggl e6b9d4f273 Renamed the to>Entity Name>() functiuons to toEntity()
ref #76
2021-01-10 16:31:55 +01:00
niggl a00231dd3c Updated imports
ref #76
2021-01-10 16:23:09 +01:00
niggl 3bc172e7e0 Intruduced a new folder structure for action models
ref #76
2021-01-10 16:10:02 +01:00
niggl ee9df21ae5 Fixed some typos in errors
ref #76
2021-01-10 16:07:37 +01:00
niggl f96b256ad3 Fixed some typos and extended comments for the middlewares
ref #76
2021-01-10 16:03:56 +01:00
niggl f2c50e929e Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is passing
2021-01-09 19:05:00 +01:00
niggl 02e3239848 Reverted temporary logging 2021-01-09 19:04:07 +01:00
niggl 8a54b027d0 Reverted temporary logging
continuous-integration/drone/push Build is passing
2021-01-09 18:55:16 +01:00
niggl 3b11e896d4 Merge branch 'dev' of git.odit.services:lfk/backend into dev
continuous-integration/drone/push Build is passing
2021-01-09 18:50:50 +01:00
niggl 89926b2c31 Temporary: extended live logging 2021-01-09 18:50:48 +01:00
niggl 7b4e89555e Temporary: extended live logging
continuous-integration/drone/push Build is passing
2021-01-09 18:47:11 +01:00
niggl 1e37186247 Revert "Temporary: extended live logging"
This reverts commit 154c763719.
2021-01-09 18:45:44 +01:00
niggl 154c763719 Temporary: extended live logging
continuous-integration/drone/push Build is passing
ref lfk/frontend#28
2021-01-09 18:08:36 +01:00
niggl 80197d5834 Merge pull request 'feature/78-trackscan' (#85) from feature/78-trackscan into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #85
2021-01-09 16:33:09 +00:00
niggl 7e95103a2d added trackscan update tests
continuous-integration/drone/pr Build is passing
ref #78
2021-01-09 17:18:33 +01:00
niggl efe1a1f543 added trackscan delete tests
ref #78
2021-01-09 16:56:57 +01:00
niggl 4fea690670 Added missing parameter fro negative-test
ref #78
2021-01-09 16:54:19 +01:00
niggl f1dee1061d added trackscan get tests
ref #78
2021-01-09 16:49:17 +01:00
niggl 61cf0fc08d Implemented proper scan invalidation
ref #78
2021-01-09 16:47:54 +01:00
niggl 0c86e5dae1 added trackscan add tests
ref #78
2021-01-09 16:44:52 +01:00
niggl 638898fa28 Implemented trackscan updateing
ref #78
2021-01-09 16:17:50 +01:00
niggl e7cd68e1c8 removed distance checks from tests
ref #78
2021-01-09 15:59:36 +01:00
niggl e40e6faebd Merge branch 'feature/78-trackscan' of git.odit.services:lfk/backend into feature/78-trackscan
# Conflicts:
#	src/controllers/RunnerController.ts
2021-01-09 15:45:35 +01:00
niggl 3d07aac944 Implemented cascading scan, track and card deletion
ref #78
2021-01-09 15:45:17 +01:00
niggl 1a5493facf Implemented cascading scan, track and card deletion
ref #78
2021-01-09 15:43:52 +01:00
niggl 9013b9492c Fixed runner distance resolution
ref #78
2021-01-09 15:25:11 +01:00
niggl 188f26ad65 Fixed manual trackscan creation
ref #78
2021-01-09 14:52:08 +01:00
niggl 3ceb5a0c0f Removed total distance from tests
ref #78
2021-01-09 14:24:16 +01:00
niggl e1ce052d3c Fixed runner total distance not getting resolved
ref #78
2021-01-09 14:23:47 +01:00
niggl 70a379edef Merge pull request 'New feature: runner cards (feature/77-runner_cards)' (#84) from feature/77-runner_cards into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #84
2021-01-09 13:01:50 +00:00
niggl 35ea3154d1 Added card update tests
continuous-integration/drone/pr Build is passing
ref #77
2021-01-09 12:42:41 +01:00
niggl ebf66821a2 Added card delete tests
ref #77
2021-01-09 12:41:59 +01:00
niggl 8463bee253 added card add tests
ref #77
2021-01-09 12:32:47 +01:00
niggl 860680d001 Implmented the EAN generation
ref #77
2021-01-09 12:24:05 +01:00
niggl df39166279 Added card get tests
ref #77
2021-01-09 11:59:20 +01:00
niggl 32fda46f0a Implemented runner updateing
ref #77
2021-01-09 11:55:32 +01:00
niggl 36ecae7e6e Added card creation
#17
2021-01-09 11:48:13 +01:00
niggl a5bfe4e3d5 Added card deletion + errors
ref #77
2021-01-09 11:28:59 +01:00
niggl 4faeddc3f3 Added runner card get endpoints
ref #77
2021-01-09 11:23:12 +01:00
niggl 98f7bf366f Added card permission target
ref #77
2021-01-09 11:21:52 +01:00
niggl af3a9e5ce2 Added basic response calss for runner cards
ref #77
2021-01-09 11:15:29 +01:00
niggl 52eb7b1afe Added a barebones runnercard controller
ref #77
2021-01-09 11:10:05 +01:00
niggl 490fbd241d Merge pull request 'Alpha Release 0.0.10' (#83) from dev into main
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #83
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-08 20:15:27 +00:00
niggl f132131156 Version bump
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-08 21:14:20 +01:00
niggl c1e680a063 Fixed responsescheme for the user controller
continuous-integration/drone/push Build is failing
2021-01-08 21:13:41 +01:00
niggl c66b06c2c9 Merge pull request 'Alpha Release 0.0.9' (#82) from dev into main
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #82
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-08 19:52:55 +00:00
niggl 65e605cdc4 Version bump
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-08 20:49:19 +01:00
niggl d2fdb4efd9 Merge pull request 'All users get profile pics feature/79-profile_pics' (#81) from feature/79-profile_pics into dev
continuous-integration/drone/push Build is failing
Reviewed-on: #81
closes #79
2021-01-08 19:48:22 +00:00
niggl d0deb9d647 Fixed wrong relation getting resolved
continuous-integration/drone/pr Build is passing
ref #79
2021-01-08 20:40:15 +01:00
niggl 5495c90eaf Merge branch 'dev' into feature/79-profile_pics
continuous-integration/drone/pr Build is failing
2021-01-08 20:19:08 +01:00
niggl bf3ffae67c Merge pull request 'Added scan (station) apis feature/67-scan_apis' (#80) from feature/67-scan_apis into dev
continuous-integration/drone/push Build is passing
Reviewed-on: #80
closes #67
2021-01-08 19:18:39 +00:00
niggl aa0337ea33 Fixed getting all permissions for users
ref #79
2021-01-08 20:17:05 +01:00
niggl 4991d735bf Pinned sqlite3 to 5.0.0 as a temporary bugfix
continuous-integration/drone/pr Build is passing
ref #67
2021-01-08 20:04:04 +01:00
niggl 398e61bddb Merge branch 'feature/67-scan_apis' of git.odit.services:lfk/backend into feature/67-scan_apis
continuous-integration/drone/pr Build is failing
# Conflicts:
#	.drone.yml
2021-01-08 19:37:25 +01:00
niggl e6576f4a54 Finned node version for ci
ref #67
2021-01-08 19:37:13 +01:00
niggl c3b9e135b0 Finned node version for ci
continuous-integration/drone/pr Build is failing
ref #67
2021-01-08 19:34:39 +01:00
niggl 3bd4948c43 Merge branch 'feature/79-profile_pics' of git.odit.services:lfk/backend into feature/79-profile_pics 2021-01-08 19:32:13 +01:00
niggl f3cd1380be First part of the permission return (buggy!)
ref #79
2021-01-08 19:32:11 +01:00
niggl a2c3dfbf85 First part of the permission return (buggy!)
ref #71
2021-01-08 19:32:04 +01:00
niggl 3c37aafe1f Added profile pics to all user related models
ref #79
2021-01-08 19:11:50 +01:00
niggl c591c182b3 Updated comments
continuous-integration/drone/pr Build is failing
ref #67
2021-01-08 18:37:33 +01:00
niggl 9cc50078d1 Merge branch 'dev' into feature/67-scan_apis 2021-01-08 18:29:33 +01:00
niggl 7728759bcd Added openapi sec scheme for the scan station auth
ref #67
2021-01-08 18:28:35 +01:00
niggl ce8fed350e Updated OPENAPI Descriptions for the new controllers
ref #67
2021-01-08 18:25:29 +01:00
niggl a005945e9e Added scan add tests with the station based auth
ref #67
2021-01-08 18:09:47 +01:00
niggl cf86520fae Fixed wrong auth type being used
ref #67
2021-01-08 18:08:13 +01:00
niggl db6fdf6baf Implemented scan auth middleware
ref #67
2021-01-08 17:50:29 +01:00
niggl 975ad50afc Added scan update tests
ref #67
2021-01-08 17:42:05 +01:00
niggl 0c27df7754 Added scan add tests
ref #67
2021-01-08 17:27:56 +01:00
niggl 102a860ba3 Added scan delete tests
ref #67
2021-01-08 16:47:52 +01:00
niggl 3a886714a0 Merge branch 'feature/67-scan_apis' of git.odit.services:lfk/backend into feature/67-scan_apis
# Conflicts:
#	src/tests/scanstations/scanstations_delete.spec.ts
2021-01-07 20:35:05 +01:00
niggl 09ab638239 Added scan station delete tests
ref #67
2021-01-07 20:34:48 +01:00
niggl a4f88c78f4 Added scan station delete tests
ref #67
2021-01-07 20:34:36 +01:00
niggl ccf2a3b617 Added scan station update tests
ref #67
2021-01-07 20:31:29 +01:00
niggl c8f941a779 Fixed wrong error getting thrown
ref #67
2021-01-07 20:22:58 +01:00
niggl 5510cbb8e9 Added scan station add tests
ref #67
2021-01-07 20:16:14 +01:00
niggl a434173b54 Added scan station get tests
ref #67
2021-01-07 20:04:15 +01:00
niggl 7387f700fb Added alias for posting track scans
ref #67
2021-01-07 19:46:20 +01:00
niggl 4f01baaa23 Added the enabled flag for scanstations
ref #67
2021-01-07 19:37:15 +01:00
niggl 09b37f0ff2 Fixed typo
ref #67
2021-01-07 19:36:57 +01:00
niggl 324d5709e3 Added tmp files to gitignore
ref #67
2021-01-07 19:19:21 +01:00
niggl 3f23e4f1f1 Added scan get tests
ref #67
2021-01-07 19:18:26 +01:00
niggl 9776a35f9f Track deletion now recognizes associated stations
ref #67
2021-01-07 18:53:09 +01:00
niggl 9b9ee70288 Implemented cascading station deletion
ref #67
2021-01-07 18:48:58 +01:00
niggl 2628f69651 Implemented scan station creation
ref #67
2021-01-07 18:39:38 +01:00
niggl b9c0a32862 Implemented single scan station get +e errors
ref #67
2021-01-07 18:35:19 +01:00
niggl 82644a2ff4 Implmented getting all scan stations
ref #67
2021-01-07 18:05:54 +01:00
niggl 3d2c93b5ac Added (scan) stations as a new permission target
ref #67
2021-01-07 17:35:36 +01:00
niggl c447114297 Added a ScanStation response class
ref #67
2021-01-07 17:31:44 +01:00
niggl 857de9ffcc Added Creation class for ScanSatations
ref #67
2021-01-07 17:29:22 +01:00
niggl eea656bd7b Added a barebones scanstation controller
ref #67
2021-01-07 17:16:36 +01:00
niggl eec5284306 Implemented "normal" scan updateing
ref #67
2021-01-07 17:12:12 +01:00
niggl 88a6a768c4 Implemented scan deletion
ref #67
2021-01-07 17:03:40 +01:00
niggl edac1a224c Fixed runner scan validation bug
ref #67
2021-01-07 16:59:57 +01:00
niggl e67d1c5697 Fixed scan runner in response
ref #67
2021-01-07 16:38:41 +01:00
niggl 30502ec949 Fixed Creation of normal scans
ref #67
2021-01-07 16:32:16 +01:00
niggl a2c3913601 Merge branch 'feature/67-scan_apis' of git.odit.services:lfk/backend into feature/67-scan_apis 2021-01-07 16:13:44 +01:00
niggl f1c7713da2 Adusted the way scan distances are implemented
ref #67
2021-01-07 16:13:41 +01:00
niggl d6a41d5a82 Ajusted the way scan distances are implemented 2021-01-07 16:13:31 +01:00
niggl 72b5ca4153 Added basics for scan creation (to be tested after scanstations got added)
ref #67
2021-01-06 19:44:20 +01:00
niggl aeec2e1c32 Added single scan get w/ errors
ref #67
2021-01-03 19:36:38 +01:00
niggl f9889bea3d Implemented scans get including the response classes
ref #67
2021-01-03 19:26:06 +01:00
niggl 2cad2ac2e9 Implemented the second round of the toResponse normalisationf for all classes
ref #67
2021-01-03 19:18:31 +01:00
niggl d948fe2631 Merge pull request 'Fixed relative paths not being updated + version bump for bugfix release' (#75) from dev into main
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #75
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-03 18:13:10 +00:00
niggl 2b5525323b Fixed relative paths not being updated + version bump for bugfix release
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-03 19:10:46 +01:00
niggl 58156e0d61 Implemented the first route of the toResponse normalisationf for all classes
ref #67
2021-01-03 19:09:06 +01:00
niggl a4b0dfe43e Defined responses for scans and trackscans
ref #67
2021-01-03 19:02:06 +01:00
niggl ee2433a5ae Added barebones scans controller
ref #67
2021-01-03 18:49:33 +01:00
niggl 2151b8502d Added Scan permission target
ref #67
2021-01-03 18:48:05 +01:00
niggl b57fde9b0a Merge pull request 'Bugfix for the openapi exporter' (#74) from dev into main
continuous-integration/drone/push Build is passing
Reviewed-on: #74
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-03 17:29:35 +00:00
niggl 86706f9422 Merge branch 'main' into dev
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-03 17:29:24 +00:00
niggl 0687f268fc Fixed switch up between node/js and ts-node/ts
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-03 18:27:58 +01:00
226 changed files with 12135 additions and 7296 deletions
+82 -15
View File
@@ -1,5 +1,27 @@
---
kind: secret
name: docker_username
get:
path: odit-registry-builder
name: username
---
kind: secret
name: docker_password
get:
path: odit-registry-builder
name: password
---
kind: secret
name: git_ssh
get:
path: odit-git-bot
name: sshkey
---
kind: pipeline
type: kubernetes
name: tests:node_latest
clone:
disable: true
@@ -9,9 +31,8 @@ steps:
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout $DRONE_SOURCE_BRANCH
- mv .env.ci .env
- name: run tests
image: node:alpine
image: node:latest
commands:
- yarn
- yarn test:ci
@@ -21,25 +42,49 @@ trigger:
---
kind: pipeline
type: docker
type: kubernetes
name: build:dev
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout dev
- name: build dev
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: DOCKER_REGISTRY_USER
from_secret: docker_username
password:
from_secret: DOCKER_REGISTRY_PASSWORD
from_secret: docker_password
repo: registry.odit.services/lfk/backend
tags:
- dev
registry: registry.odit.services
mtu: 1000
- name: run changelog export
depends_on: ["clone"]
image: node:latest
commands:
- npx auto-changelog --commit-limit false -p -u --hide-credit
- name: push new changelog to repo
depends_on: ["run changelog export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: 🧾New changelog file version [CI SKIP] [skip ci]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/backend.git
ssh_key:
from_secret: git_ssh
- name: run full license export
depends_on: ["clone"]
image: node:alpine
image: node:14.15.1-alpine3.12
commands:
- yarn
- yarn licenses:export
@@ -49,11 +94,13 @@ steps:
settings:
branch: dev
commit: true
commit_message: new license file version [CI SKIP]
commit_message: 📖New license file version [CI SKIP] [skip ci]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/backend.git
skip_verify: true
ssh_key:
from_secret: GITLAB_SSHKEY
from_secret: git_ssh
trigger:
branch:
@@ -63,22 +110,41 @@ trigger:
---
kind: pipeline
type: docker
type: kubernetes
name: build:latest
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout dev
- git merge main
- git checkout main
- name: build latest
depends_on: ["clone"]
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: DOCKER_REGISTRY_USER
from_secret: docker_username
password:
from_secret: DOCKER_REGISTRY_PASSWORD
from_secret: docker_password
repo: registry.odit.services/lfk/backend
tags:
- latest
registry: registry.odit.services
mtu: 1000
- name: push merge to repo
depends_on: ["clone"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: false
remote: git@git.odit.services:lfk/backend.git
ssh_key:
from_secret: git_ssh
trigger:
branch:
@@ -88,7 +154,7 @@ trigger:
---
kind: pipeline
type: docker
type: kubernetes
name: build:tags
steps:
@@ -97,13 +163,14 @@ steps:
depends_on: [clone]
settings:
username:
from_secret: DOCKER_REGISTRY_USER
from_secret: docker_username
password:
from_secret: DOCKER_REGISTRY_PASSWORD
from_secret: docker_password
repo: registry.odit.services/lfk/backend
tags:
- '${DRONE_TAG}'
registry: registry.odit.services
mtu: 1000
- name: trigger node lib build
image: idcooldi/drone-webhook
settings:
+1 -1
View File
@@ -6,4 +6,4 @@ DB_USER=unused
DB_PASSWORD=bla
DB_NAME=./test.sqlite
NODE_ENV=dev
POSTALCODE_COUNTRYCODE=null
POSTALCODE_COUNTRYCODE=DE
+4 -3
View File
@@ -1,9 +1,10 @@
APP_PORT=4010
DB_TYPE=bla
DB_TYPE=sqlite
DB_HOST=bla
DB_PORT=bla
DB_USER=bla
DB_PASSWORD=bla
DB_NAME=bla
DB_NAME=./test.sqlite
NODE_ENV=production
POSTALCODE_COUNTRYCODE=null
POSTALCODE_COUNTRYCODE=DE
SEED_TEST_DATA=false
+2 -1
View File
@@ -134,4 +134,5 @@ build
*.sqlite-jurnal
/docs
lib
/oss-attribution
/oss-attribution
*.tmp
+1445
View File
File diff suppressed because it is too large Load Diff
+66 -35
View File
@@ -2,20 +2,18 @@
Backend Server
## Quickstart 🐳
> Use this to run the backend with a postgresql db in docker
1. Clone the repo or copy the docker-compose
2. Run in toe folder that contains the docker-compose file: `docker-compose up -d`
3. Visit http://127.0.0.1:4010/api/docs to check if the server is running
4. You can now use the default admin user (`demo:demo`)
## Dev Setup 🛠
> Local dev setup utilizing sqlite3 as the database.
### Local w/ sqlite
1. Create a .env file in the project root containing:
```
APP_PORT=4010
DB_TYPE=sqlite
DB_HOST=bla
DB_PORT=bla
DB_USER=bla
DB_PASSWORD=bla
DB_NAME=./test.sqlite
```
1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed)
2. Install Dependencies
```bash
yarn
@@ -25,16 +23,52 @@ Backend Server
yarn dev
```
### Generate Docs
### Run Tests
```bash
# Run tests once (server has to run)
yarn test
# Run test in watch mode (reruns on change)
yarn test:watch
# Run test in ci mode (automaticly starts the dev server)
yarn test:ci
```
### Use your own mail templates
> You use your own mail templates by replacing the default ones we provided (either in-code or by mounting them into the /app/static/mail_templates folder).
The mail templates always come in a .html and a .txt variant to provide compatability with legacy mail clients.
Currently the following templates exist:
* pw-reset.(html/txt)
### Generate Docs
```bash
yarn docs
```
### Docker w/ postgres 🐳
## ENV Vars
> You can provide them via .env file or docker env vars.
> You can use the `test:ci:generate_env` package script to generate a example env (uses bs data as test server and ignores the errors).
| Name | Type | Default | Description
| - | - | - | -
| APP_PORT | Number | 4010 | The port the backend server listens on. Is optional.
| DB_TYPE | String | N/A | The type of the db u want to use. It has to be supported by typeorm. Possible: `sqlite`, `mysql`, `postgresql`
| DB_HOST | String | N/A | The db's host's ip-address/fqdn or file path for sqlite
| DB_PORT | String | N/A | The db's port
| DB_USER | String | N/A | The user for accessing the db
| DB_PASSWORD | String | N/A | The user's password for accessing the db
| DB_NAME | String | N/A | The db's name
| NODE_ENV | String | dev | The apps env - influences debug info. Also when the env is set to "test", mailing errors get ignored.
| POSTALCODE_COUNTRYCODE | String/CountryCode | N/A | The countrycode used to validate address's postal codes
| PHONE_COUNTRYCODE | String/CountryCode | null (international) | The countrycode used to validate phone numers
| SEED_TEST_DATA | Boolean | False | If you want the app to seed some example data set this to true
| MAILER_URL | String(Url) | N/A | The mailer's base url (no trailing slash)
| MAILER_KEY | String | N/A | The mailer's api key.
| IMPRINT_URL | String(Url) | /imprint | The link to a imprint page for the system (Defaults to the frontend's imprint)
| PRIVACY_URL | String(Url) | /privacy | The link to a privacy page for the system (Defaults to the frontend's privacy page)
```bash
docker-compose up --build
```
## Recommended Editor
@@ -42,22 +76,19 @@ docker-compose up --build
### Recommended Extensions
- will be automatically recommended via ./vscode/extensions.json
* will be automatically recommended via ./vscode/extensions.json
## Branches
- main: Protected "release" branch
- dev: Current dev branch for merging the different features - only push for merges or minor changes!
- feature/xyz: Feature branches - `feature/issueid-title`
- bugfix/xyz: Branches for bugfixes - `bugfix/issueid-title` (no id for readme changes needed)
## File Structure
- src/models/entities\* - database models (typeorm entities)
- src/models/actions\* - actions models
- src/models/responses\* - response models
- src/controllers/\* - routing-controllers
- src/loaders/\* - loaders for the different init steps of the api server
- src/middlewares/\* - express middlewares (mainly auth r/n)
- src/errors/* - our custom (http) errors
- src/routes/\* - express routes for everything we don't do via routing-controllers (depreciated)
## Staging
### Branches & Tags
* vX.Y.Z: Release tags created from the main branch
* The version numbers follow the semver standard
* A new release tag automaticly triggers the release ci pipeline
* main: Protected "release" branch
* The latest tag of the docker image get's build from this
* New releases get created as tags from this
* dev: Current dev branch for merging the different feature branches and bugfixes
* The dev tag of the docker image get's build from this
* Only push minor changes to this branch!
* To merge a feature branch into this please create a pull request
* feature/xyz: Feature branches - nameing scheme: `feature/issueid-title`
* bugfix/xyz: Branches for bugfixes - nameing scheme:`bugfix/issueid-title`
+123 -44
View File
@@ -1,3 +1,32 @@
# @odit/class-validator-jsonschema
**Author**: Aleksi Pekkala <aleksipekkala@gmail.com>
**Repo**: git@github.com:epiphone/class-validator-jsonschema.git
**License**: MIT
**Description**: Convert class-validator-decorated classes into JSON schema
## License Text
MIT License
Copyright (c) 2017 Aleksi Pekkala
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# argon2
**Author**: Ranieri Althoff <ranisalt+argon2@gmail.com>
**Repo**: [object Object]
@@ -28,6 +57,33 @@ SOFTWARE.
# axios
**Author**: Matt Zabriskie
**Repo**: [object Object]
**License**: MIT
**Description**: Promise based HTTP client for the browser and node.js
## License Text
Copyright (c) 2014-present Matt Zabriskie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# body-parser
**Author**: undefined
**Repo**: expressjs/body-parser
@@ -88,22 +144,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# class-validator
**Author**: [object Object]
**Author**: TypeStack contributors
**Repo**: [object Object]
**License**: MIT
**Description**: Class-based validation with Typescript / ES6 / ES5 using decorators or validation schemas. Supports both node.js and browser
**Description**: Decorator-based property validation for classes.
## License Text
# class-validator-jsonschema
**Author**: Aleksi Pekkala <aleksipekkala@gmail.com>
**Repo**: git@github.com:epiphone/class-validator-jsonschema.git
**License**: MIT
**Description**: Convert class-validator-decorated classes into JSON schema
## License Text
MIT License
The MIT License
Copyright (c) 2017 Aleksi Pekkala
Copyright (c) 2015-2020 TypeStack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -112,17 +161,16 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# consola
**Author**: undefined
@@ -332,6 +380,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# libphonenumber-js
**Author**: catamphetamine <purecatamphetamine@gmail.com>
**Repo**: [object Object]
**License**: MIT
**Description**: A simpler (and smaller) rewrite of Google Android's libphonenumber library in javascript
## License Text
(The MIT License)
Copyright (c) 2016 @catamphetamine <purecatamphetamine@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# mysql
**Author**: Felix Geisendörfer <felix@debuggable.com> (http://debuggable.com/)
**Repo**: mysqljs/mysql
@@ -594,7 +671,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**Author**: ODIT.Services
**Repo**: [object Object]
**License**: MIT
**Description**: A simple license crawler
**Description**: A simple license crawler for crediting open source work
## License Text
MIT License Copyright (c) 2020 ODIT.Services (info@odit.services)
@@ -820,33 +897,6 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE
# axios
**Author**: Matt Zabriskie
**Repo**: [object Object]
**License**: MIT
**Description**: Promise based HTTP client for the browser and node.js
## License Text
Copyright (c) 2014-present Matt Zabriskie
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# cp-cli
**Author**: undefined
**Repo**: [object Object]
@@ -934,6 +984,35 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# release-it
**Author**: [object Object]
**Repo**: [object Object]
**License**: MIT
**Description**: Generic CLI tool to automate versioning and package publishing related tasks.
## License Text
MIT License
Copyright (c) 2018 Lars Kappert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# rimraf
**Author**: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
**Repo**: git://github.com/isaacs/rimraf.git
+45 -27
View File
@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-backend",
"version": "0.0.7",
"version": "0.7.1",
"main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend",
"author": {
@@ -22,11 +22,12 @@
],
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"argon2": "^0.27.0",
"@odit/class-validator-jsonschema": "2.1.1",
"argon2": "^0.27.1",
"axios": "^0.21.1",
"body-parser": "^1.19.0",
"class-transformer": "^0.3.1",
"class-validator": "^0.12.2",
"class-validator-jsonschema": "^2.0.3",
"class-transformer": "0.3.1",
"class-validator": "^0.13.1",
"consola": "^2.15.0",
"cookie": "^0.4.1",
"cookie-parser": "^1.4.5",
@@ -35,37 +36,38 @@
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"libphonenumber-js": "^1.9.9",
"mysql": "^2.18.1",
"pg": "^8.5.1",
"reflect-metadata": "^0.1.13",
"routing-controllers": "^0.9.0-alpha.6",
"routing-controllers-openapi": "^2.1.0",
"sqlite3": "^5.0.0",
"typeorm": "^0.2.29",
"routing-controllers": "0.9.0-alpha.6",
"routing-controllers-openapi": "^2.2.0",
"sqlite3": "5.0.0",
"typeorm": "^0.2.30",
"typeorm-routing-controllers-extensions": "^0.2.0",
"typeorm-seeding": "^1.6.1",
"uuid": "^8.3.1",
"uuid": "^8.3.2",
"validator": "^13.5.2"
},
"devDependencies": {
"@odit/license-exporter": "^0.0.8",
"@types/cors": "^2.8.8",
"@odit/license-exporter": "^0.0.9",
"@types/cors": "^2.8.9",
"@types/csvtojson": "^1.1.5",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.16",
"@types/express": "^4.17.11",
"@types/jest": "^26.0.20",
"@types/jsonwebtoken": "^8.5.0",
"@types/node": "^14.14.9",
"@types/node": "^14.14.22",
"@types/uuid": "^8.3.0",
"axios": "^0.21.0",
"cp-cli": "^2.0.0",
"jest": "^26.6.3",
"nodemon": "^2.0.6",
"rimraf": "^2.7.1",
"start-server-and-test": "^1.11.6",
"ts-jest": "^26.4.4",
"ts-node": "^9.0.0",
"typedoc": "^0.19.2",
"typescript": "^4.1.2"
"nodemon": "^2.0.7",
"release-it": "^14.2.2",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.11.7",
"ts-jest": "^26.5.0",
"ts-node": "^9.1.1",
"typedoc": "^0.20.19",
"typescript": "^4.1.3"
},
"scripts": {
"dev": "nodemon src/app.ts",
@@ -73,10 +75,26 @@
"docs": "typedoc --out docs src",
"test": "jest",
"test:watch": "jest --watchAll",
"test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
"test:ci:generate_env": "ts-node scripts/create_testenv.ts",
"test:ci:run": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
"test:ci": "npm run test:ci:generate_env && npm run test:ci:run",
"seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
"openapi:export": "node scripts/openapi_export.js",
"licenses:export": "license-exporter --md"
"openapi:export": "ts-node scripts/openapi_export.ts",
"licenses:export": "license-exporter --markdown",
"release": "release-it --only-version"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀Bumped version to v${version}",
"requireBranch": "dev",
"push": false,
"tag": false
},
"npm": {
"publish": false
}
},
"nodemonConfig": {
"ignore": [
@@ -84,4 +102,4 @@
"docs/*"
]
}
}
}
+24
View File
@@ -0,0 +1,24 @@
import consola from "consola";
import fs from "fs";
const env = `
APP_PORT=4010
DB_TYPE=sqlite
DB_HOST=bla
DB_PORT=bla
DB_USER=bla
DB_PASSWORD=bla
DB_NAME=./test.sqlite
NODE_ENV=test
POSTALCODE_COUNTRYCODE=DE
SEED_TEST_DATA=true
MAILER_URL=https://dev.lauf-fuer-kaya.de/mailer
MAILER_KEY=asdasd`;
try {
fs.writeFileSync("./.env", env, { encoding: "utf-8" });
consola.success("Exported ci env to .env");
} catch (error) {
consola.error("Couldn't export the ci env");
}
+4 -38
View File
@@ -1,9 +1,9 @@
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
import { validationMetadatasToSchemas } from '@odit/class-validator-jsonschema';
import consola from "consola";
import fs from "fs";
import "reflect-metadata";
import { createExpressServer, getMetadataArgsStorage } from "routing-controllers";
import { routingControllersToSpec } from 'routing-controllers-openapi';
import { generateSpec } from '../src/apispec';
import { config } from '../src/config';
import authchecker from "../src/middlewares/authchecker";
import { ErrorHandler } from '../src/middlewares/ErrorHandler';
@@ -15,7 +15,7 @@ createExpressServer({
development: config.development,
cors: true,
routePrefix: "/api",
controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
controllers: [`${__dirname}/../src/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
});
const storage = getMetadataArgsStorage();
@@ -24,41 +24,7 @@ const schemas = validationMetadatasToSchemas({
});
//Spec creation based on the previously created schemas
const spec = routingControllersToSpec(
storage,
{
routePrefix: "/api"
},
{
components: {
schemas,
"securitySchemes": {
"AuthToken": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
},
"RefreshTokenCookie": {
"type": "apiKey",
"in": "cookie",
"name": "lfk_backend__refresh_token",
description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
},
"StatsApiToken": {
"type": "http",
"scheme": "bearer",
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients)."
}
}
},
info: {
description: "The the backend API for the LfK! runner system.",
title: "LfK! Backend API",
version: "0.0.5",
},
}
);
const spec = generateSpec(storage, schemas);
try {
fs.writeFileSync("./openapi.json", JSON.stringify(spec), { encoding: "utf-8" });
+51
View File
@@ -0,0 +1,51 @@
import { MetadataArgsStorage } from 'routing-controllers';
import { routingControllersToSpec } from 'routing-controllers-openapi';
import { config } from './config';
/**
* This function generates a the openapi spec from route metadata and type schemas.
* @param storage MetadataArgsStorage object generated by routing-controllers.
* @param schemas MetadataArgsStorage object generated by class-validator-jsonschema.
*/
export function generateSpec(storage: MetadataArgsStorage, schemas) {
return routingControllersToSpec(
storage,
{
routePrefix: "/api"
},
{
components: {
schemas,
"securitySchemes": {
"AuthToken": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
},
"RefreshTokenCookie": {
"type": "apiKey",
"in": "cookie",
"name": "lfk_backend__refresh_token",
description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
},
"StatsApiToken": {
"type": "http",
"scheme": "bearer",
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients). Only valid for obtaining stats."
},
"StationApiToken": {
"type": "http",
"scheme": "bearer",
description: "Api token that can be obtained by creating a new scan station (post to /api/stations). Only valid for creating scans."
}
}
},
info: {
description: `The the backend API for the LfK! runner system. <br>[Imprint](${config.imprint_url}) & [Privacy](${config.privacy_url})`,
title: "LfK! Backend API",
version: config.version
},
}
);
}
+5
View File
@@ -5,10 +5,12 @@ import { config, e as errors } from './config';
import loaders from "./loaders/index";
import authchecker from "./middlewares/authchecker";
import { ErrorHandler } from './middlewares/ErrorHandler';
import UserChecker from './middlewares/UserChecker';
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
const app = createExpressServer({
authorizationChecker: authchecker,
currentUserChecker: UserChecker,
middlewares: [ErrorHandler],
development: config.development,
cors: true,
@@ -18,6 +20,9 @@ const app = createExpressServer({
async function main() {
await loaders(app);
if (config.testing) {
consola.info("🛠[config]: Discovered testing env. Mailing errors will get ignored!")
}
app.listen(config.internal_port, () => {
consola.success(
`⚡️[server]: Server is running at http://localhost:${config.internal_port}`
+24 -8
View File
@@ -1,27 +1,36 @@
import { config as configDotenv } from 'dotenv';
import { CountryCode } from 'libphonenumber-js';
import ValidatorJS from 'validator';
configDotenv();
export const config = {
internal_port: parseInt(process.env.APP_PORT) || 4010,
development: process.env.NODE_ENV === "production",
testing: process.env.NODE_ENV === "test",
jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ",
postalcode_validation_countrycode: getPostalCodeLocale()
phone_validation_countrycode: getPhoneCodeLocale(),
postalcode_validation_countrycode: getPostalCodeLocale(),
version: process.env.VERSION || require('../package.json').version,
seedTestData: getDataSeeding(),
app_url: process.env.APP_URL || "http://localhost:8080",
privacy_url: process.env.PRIVACY_URL || "/privacy",
imprint_url: process.env.IMPRINT_URL || "/imprint",
mailer_url: process.env.MAILER_URL || "",
mailer_key: process.env.MAILER_KEY || ""
}
let errors = 0
if (typeof config.internal_port !== "number") {
errors++
}
if (typeof config.phone_validation_countrycode !== "string") {
errors++
}
if (config.phone_validation_countrycode.length !== 2) {
errors++
}
if (typeof config.development !== "boolean") {
errors++
}
if (config.mailer_url == "" || config.mailer_key == "") {
errors++;
}
function getPhoneCodeLocale(): CountryCode {
return (process.env.PHONE_COUNTRYCODE as CountryCode);
}
function getPostalCodeLocale(): any {
try {
const stringArray: String[] = ValidatorJS.isPostalCodeLocales;
@@ -31,4 +40,11 @@ function getPostalCodeLocale(): any {
return null;
}
}
function getDataSeeding(): Boolean {
try {
return JSON.parse(process.env.SEED_TEST_DATA);
} catch (error) {
return false;
}
}
export let e = errors
+106 -103
View File
@@ -1,103 +1,106 @@
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
import { UserNotFoundError } from '../errors/UserErrors';
import { CreateAuth } from '../models/actions/CreateAuth';
import { CreateResetToken } from '../models/actions/CreateResetToken';
import { HandleLogout } from '../models/actions/HandleLogout';
import { RefreshAuth } from '../models/actions/RefreshAuth';
import { ResetPassword } from '../models/actions/ResetPassword';
import { Auth } from '../models/responses/ResponseAuth';
import { Logout } from '../models/responses/ResponseLogout';
@JsonController('/auth')
export class AuthController {
constructor() {
}
@Post("/login")
@ResponseSchema(Auth)
@ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
let auth;
try {
auth = await createAuth.toAuth();
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
return response.send(auth)
} catch (error) {
throw error;
}
}
@Post("/logout")
@ResponseSchema(Logout)
@ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
handleLogout.token = refresh_token;
}
let logout;
try {
logout = await handleLogout.logout()
await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true });
} catch (error) {
throw error;
}
return response.send(logout)
}
@Post("/refresh")
@ResponseSchema(Auth)
@ResponseSchema(JwtNotProvidedError)
@ResponseSchema(IllegalJWTError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(RefreshTokenCountInvalidError)
@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
refreshAuth.token = refresh_token;
}
let auth;
try {
auth = await refreshAuth.toAuth();
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
} catch (error) {
throw error;
}
return response.send(auth)
}
@Post("/reset")
@ResponseSchema(Auth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) {
//This really shouldn't just get returned, but sent via mail or sth like that. But for dev only this is fine.
return { "resetToken": await passwordReset.toResetToken() };
}
@Post("/reset/:token")
@ResponseSchema(Auth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
passwordReset.resetToken = token;
return await passwordReset.resetPassword();
}
}
import { Body, CookieParam, JsonController, Param, Post, QueryParam, Req, Res } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
import { MailSendingError } from '../errors/MailErrors';
import { UserNotFoundError } from '../errors/UserErrors';
import { Mailer } from '../mailer';
import { CreateAuth } from '../models/actions/create/CreateAuth';
import { CreateResetToken } from '../models/actions/create/CreateResetToken';
import { HandleLogout } from '../models/actions/HandleLogout';
import { RefreshAuth } from '../models/actions/RefreshAuth';
import { ResetPassword } from '../models/actions/ResetPassword';
import { ResponseAuth } from '../models/responses/ResponseAuth';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { Logout } from '../models/responses/ResponseLogout';
@JsonController('/auth')
export class AuthController {
@Post("/login")
@ResponseSchema(ResponseAuth)
@ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
let auth;
try {
auth = await createAuth.toAuth();
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
return response.send(auth)
} catch (error) {
throw error;
}
}
@Post("/logout")
@ResponseSchema(Logout)
@ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
handleLogout.token = refresh_token;
}
let logout;
try {
logout = await handleLogout.logout()
await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true });
} catch (error) {
throw error;
}
return response.send(logout)
}
@Post("/refresh")
@ResponseSchema(ResponseAuth)
@ResponseSchema(JwtNotProvidedError)
@ResponseSchema(IllegalJWTError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(RefreshTokenCountInvalidError)
@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
refreshAuth.token = refresh_token;
}
let auth;
try {
auth = await refreshAuth.toAuth();
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
} catch (error) {
throw error;
}
return response.send(auth)
}
@Post("/reset")
@ResponseSchema(ResponseEmpty, { statusCode: 200 })
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 })
@ResponseSchema(MailSendingError, { statusCode: 500 })
@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken, @QueryParam("locale") locale: string = "en") {
const reset_token: string = await passwordReset.toResetToken();
await Mailer.sendResetMail(passwordReset.email, reset_token, locale);
return new ResponseEmpty();
}
@Post("/reset/:token")
@ResponseSchema(ResponseAuth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
passwordReset.resetToken = token;
return await passwordReset.resetPassword();
}
}
+145
View File
@@ -0,0 +1,145 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors';
import { DonorNotFoundError } from '../errors/DonorErrors';
import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation';
import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation';
import { UpdateDistanceDonation } from '../models/actions/update/UpdateDistanceDonation';
import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonation';
import { DistanceDonation } from '../models/entities/DistanceDonation';
import { Donation } from '../models/entities/Donation';
import { FixedDonation } from '../models/entities/FixedDonation';
import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation';
import { ResponseDonation } from '../models/responses/ResponseDonation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
@JsonController('/donations')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class DonationController {
private donationRepository: Repository<Donation>;
private distanceDonationRepository: Repository<DistanceDonation>;
private fixedDonationRepository: Repository<FixedDonation>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.donationRepository = getConnectionManager().get().getRepository(Donation);
this.distanceDonationRepository = getConnectionManager().get().getRepository(DistanceDonation);
this.fixedDonationRepository = getConnectionManager().get().getRepository(FixedDonation);
}
@Get()
@Authorized("DONATION:GET")
@ResponseSchema(ResponseDonation, { isArray: true })
@ResponseSchema(ResponseDistanceDonation, { isArray: true })
@OpenAPI({ description: 'Lists all donations (fixed or distance based) from all donors. <br> This includes the donations\'s runner\'s distance ran(if distance donation).' })
async getAll() {
let responseDonations: ResponseDonation[] = new Array<ResponseDonation>();
const donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
donations.forEach(donation => {
responseDonations.push(donation.toResponse());
});
return responseDonations;
}
@Get('/:id')
@Authorized("DONATION:GET")
@ResponseSchema(ResponseDonation)
@ResponseSchema(ResponseDistanceDonation)
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
@OnUndefined(DonationNotFoundError)
@OpenAPI({ description: 'Lists all information about the donation whose id got provided. This includes the donation\'s runner\'s distance ran (if distance donation).' })
async getOne(@Param('id') id: number) {
let donation = await this.donationRepository.findOne({ id: id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })
if (!donation) { throw new DonationNotFoundError(); }
return donation.toResponse();
}
@Post('/fixed')
@Authorized("DONATION:CREATE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a fixed donation (not distance donation - use /donations/distance instead). <br> Please rmemember to provide the donation\'s donors\'s id and amount.' })
async postFixed(@Body({ validate: true }) createDonation: CreateFixedDonation) {
let donation = await createDonation.toEntity();
donation = await this.fixedDonationRepository.save(donation);
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
}
@Post('/distance')
@Authorized("DONATION:CREATE")
@ResponseSchema(ResponseDistanceDonation)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a distance donation (not fixed donation - use /donations/fixed instead). <br> Please rmemember to provide the donation\'s donors\'s and runner\s ids and amount per distance (kilometer).' })
async postDistance(@Body({ validate: true }) createDonation: CreateDistanceDonation) {
let donation = await createDonation.toEntity();
donation = await this.distanceDonationRepository.save(donation);
return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
}
@Put('/fixed/:id')
@Authorized("DONATION:UPDATE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/distance instead) whose id you provided. <br> Please remember that ids can't be changed and amounts must be positive." })
async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateFixedDonation) {
let oldDonation = await this.fixedDonationRepository.findOne({ id: id });
if (!oldDonation) {
throw new DonationNotFoundError();
}
if (oldDonation.id != donation.id) {
throw new DonationIdsNotMatchingError();
}
await this.fixedDonationRepository.save(await donation.update(oldDonation));
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
}
@Put('/distance/:id')
@Authorized("DONATION:UPDATE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the distance donation (not fixed donation - use /donations/fixed instead) whose id you provided. <br> Please remember that ids can't be changed and amountPerDistance must be positive." })
async putDistance(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) {
let oldDonation = await this.distanceDonationRepository.findOne({ id: id });
if (!oldDonation) {
throw new DonationNotFoundError();
}
if (oldDonation.id != donation.id) {
throw new DonationIdsNotMatchingError();
}
await this.distanceDonationRepository.save(await donation.update(oldDonation));
return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
}
@Delete('/:id')
@Authorized("DONATION:DELETE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(ResponseDistanceDonation)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the donation whose id you provided. <br> If no donation with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let donation = await this.donationRepository.findOne({ id: id });
if (!donation) { return null; }
const responseScan = await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
await this.donationRepository.delete(donation);
return responseScan.toResponse();
}
}
+24 -16
View File
@@ -1,12 +1,13 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
import { CreateDonor } from '../models/actions/CreateDonor';
import { UpdateDonor } from '../models/actions/UpdateDonor';
import { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
import { CreateDonor } from '../models/actions/create/CreateDonor';
import { UpdateDonor } from '../models/actions/update/UpdateDonor';
import { Donor } from '../models/entities/Donor';
import { ResponseDonor } from '../models/responses/ResponseDonor';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { DonationController } from './DonationController';
@JsonController('/donors')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
@@ -23,10 +24,10 @@ export class DonorController {
@Get()
@Authorized("DONOR:GET")
@ResponseSchema(ResponseDonor, { isArray: true })
@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
@OpenAPI({ description: 'Lists all donor. <br> This includes the donor\'s current donation amount.' })
async getAll() {
let responseDonors: ResponseDonor[] = new Array<ResponseDonor>();
const donors = await this.donorRepository.find();
const donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
donors.forEach(donor => {
responseDonors.push(new ResponseDonor(donor));
});
@@ -38,9 +39,9 @@ export class DonorController {
@ResponseSchema(ResponseDonor)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@OnUndefined(DonorNotFoundError)
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
@OpenAPI({ description: 'Lists all information about the donor whose id got provided. <br> This includes the donor\'s current donation amount.' })
async getOne(@Param('id') id: number) {
let donor = await this.donorRepository.findOne({ id: id })
let donor = await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })
if (!donor) { throw new DonorNotFoundError(); }
return new ResponseDonor(donor);
}
@@ -48,17 +49,17 @@ export class DonorController {
@Post()
@Authorized("DONOR:CREATE")
@ResponseSchema(ResponseDonor)
@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' })
@OpenAPI({ description: 'Create a new donor.' })
async post(@Body({ validate: true }) createRunner: CreateDonor) {
let donor;
try {
donor = await createRunner.toDonor();
donor = await createRunner.toEntity();
} catch (error) {
throw error;
}
donor = await this.donorRepository.save(donor)
return new ResponseDonor(await this.donorRepository.findOne(donor));
return new ResponseDonor(await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
}
@Put('/:id')
@@ -66,7 +67,7 @@ export class DonorController {
@ResponseSchema(ResponseDonor)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." })
@OpenAPI({ description: "Update the donor whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) {
let oldDonor = await this.donorRepository.findOne({ id: id });
@@ -78,8 +79,8 @@ export class DonorController {
throw new DonorIdsNotMatchingError();
}
await this.donorRepository.save(await donor.updateDonor(oldDonor));
return new ResponseDonor(await this.donorRepository.findOne({ id: id }));
await this.donorRepository.save(await donor.update(oldDonor));
return new ResponseDonor(await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
}
@Delete('/:id')
@@ -87,17 +88,24 @@ export class DonorController {
@ResponseSchema(ResponseDonor)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> If no runner with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the donor whose id you provided. <br> If no donor with this id exists it will just return 204(no content). <br> If the donor still has donations associated this will fail, please provide the query param ?force=true to delete the donor with all associated donations.' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let donor = await this.donorRepository.findOne({ id: id });
if (!donor) { return null; }
const responseDonor = await this.donorRepository.findOne(donor);
const responseDonor = await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
if (!donor) {
throw new DonorNotFoundError();
}
//TODO: DELETE DONATIONS AND WARN FOR FORCE (https://git.odit.services/lfk/backend/issues/66)
const donorDonations = (await this.donorRepository.findOne({ id: donor.id }, { relations: ["donations"] })).donations;
if (donorDonations.length > 0 && !force) {
throw new DonorHasDonationsError();
}
const donationController = new DonationController();
for (let donation of donorDonations) {
await donationController.remove(donation.id, force);
}
await this.donorRepository.delete(donor);
return new ResponseDonor(responseDonor);
+107
View File
@@ -0,0 +1,107 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnection, getConnectionManager, Repository } from 'typeorm';
import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
import { UpdateGroupContact } from '../models/actions/update/UpdateGroupContact';
import { GroupContact } from '../models/entities/GroupContact';
import { RunnerGroup } from '../models/entities/RunnerGroup';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseGroupContact } from '../models/responses/ResponseGroupContact';
@JsonController('/contacts')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class GroupContactController {
private contactRepository: Repository<GroupContact>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.contactRepository = getConnectionManager().get().getRepository(GroupContact);
}
@Get()
@Authorized("CONTACT:GET")
@ResponseSchema(ResponseGroupContact, { isArray: true })
@OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' })
async getAll() {
let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>();
const contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] });
contacts.forEach(contact => {
responseContacts.push(contact.toResponse());
});
return responseContacts;
}
@Get('/:id')
@Authorized("CONTACT:GET")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
@OnUndefined(GroupContactNotFoundError)
@OpenAPI({ description: 'Lists all information about the contact whose id got provided. <br> This includes the contact\'s associated groups.' })
async getOne(@Param('id') id: number) {
let contact = await this.contactRepository.findOne({ id: id }, { relations: ['groups', 'groups.parentGroup'] })
if (!contact) { throw new GroupContactNotFoundError(); }
return contact.toResponse();
}
@Post()
@Authorized("CONTACT:CREATE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new contact.' })
async post(@Body({ validate: true }) createContact: CreateGroupContact) {
let contact;
try {
contact = await createContact.toEntity();
} catch (error) {
throw error;
}
contact = await this.contactRepository.save(contact)
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
}
@Put('/:id')
@Authorized("CONTACT:UPDATE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
@ResponseSchema(GroupContactIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@OpenAPI({ description: "Update the contact whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateGroupContact) {
let oldContact = await this.contactRepository.findOne({ id: id });
if (!oldContact) {
throw new GroupContactNotFoundError();
}
if (oldContact.id != contact.id) {
throw new GroupContactIdsNotMatchingError();
}
await this.contactRepository.save(await contact.update(oldContact));
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
}
@Delete('/:id')
@Authorized("CONTACT:DELETE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the contact whose id you provided. <br> If no contact with this id exists it will just return 204(no content). <br> This won\'t delete any groups associated with the contact.' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let contact = await this.contactRepository.findOne({ id: id });
if (!contact) { return null; }
const responseContact = await this.contactRepository.findOne(contact, { relations: ['groups', 'groups.parentGroup'] });
for (let group of responseContact.groups) {
group.contact = null;
await getConnection().getRepository(RunnerGroup).save(group);
}
await this.contactRepository.delete(contact);
return responseContact.toResponse();
}
}
+2 -2
View File
@@ -36,7 +36,7 @@ export class ImportController {
return responseRunners;
}
@Post('/organisations/:id/import')
@Post('/organizations/:id/import')
@ContentType("application/json")
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@@ -78,7 +78,7 @@ export class ImportController {
return await this.postJSON(importRunners, groupID);
}
@Post('/organisations/:id/import/csv')
@Post('/organizations/:id/import/csv')
@ContentType("application/json")
@UseBefore(RawBodyMiddleware)
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
+86
View File
@@ -0,0 +1,86 @@
import { Body, CurrentUser, Delete, Get, JsonController, OnUndefined, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UpdateUser } from '../models/actions/update/UpdateUser';
import { User } from '../models/entities/User';
import { ResponseUser } from '../models/responses/ResponseUser';
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
import { PermissionController } from './PermissionController';
@JsonController('/users/me')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class MeController {
private userRepository: Repository<User>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.userRepository = getConnectionManager().get().getRepository(User);
}
@Get('/')
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all information about yourself.' })
async get(@CurrentUser() currentUser: User) {
let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUser(user);
}
@Get('/permissions')
@ResponseSchema(ResponseUserPermissions)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all permissions granted to the you sorted into directly granted and inherited as permission response objects.' })
async getPermissions(@CurrentUser() currentUser: User) {
let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUserPermissions(user);
}
@Put('/')
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: "Update the yourself. <br> You can't edit your own permissions or group memberships here - Please use the /api/users/:id enpoint instead. <br> Please remember that ids can't be changed." })
async put(@CurrentUser() currentUser: User, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['groups'] });
updateUser.groups = oldUser.groups.map(g => g.id);
if (!oldUser) {
throw new UserNotFoundError();
}
if (oldUser.id != updateUser.id) {
throw new UserIdsNotMatchingError();
}
await this.userRepository.save(await updateUser.update(oldUser));
return new ResponseUser(await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Delete('/')
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
@OpenAPI({ description: 'Delete yourself. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to you they will get deleted as well.' })
async remove(@CurrentUser() currentUser: User, @QueryParam("force") force: boolean) {
if (!force) { throw new UserDeletionNotConfirmedError; }
if (!currentUser) { return UserNotFoundError; }
const responseUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
const permissionControler = new PermissionController();
for (let permission of responseUser.permissions) {
await permissionControler.remove(permission.id, true);
}
await this.userRepository.delete(currentUser);
return new ResponseUser(responseUser);
}
}
+5 -5
View File
@@ -3,8 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors';
import { PrincipalNotFoundError } from '../errors/PrincipalErrors';
import { CreatePermission } from '../models/actions/CreatePermission';
import { UpdatePermission } from '../models/actions/UpdatePermission';
import { CreatePermission } from '../models/actions/create/CreatePermission';
import { UpdatePermission } from '../models/actions/update/UpdatePermission';
import { Permission } from '../models/entities/Permission';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponsePermission } from '../models/responses/ResponsePermission';
@@ -58,7 +58,7 @@ export class PermissionController {
async post(@Body({ validate: true }) createPermission: CreatePermission) {
let permission;
try {
permission = await createPermission.toPermission();
permission = await createPermission.toEntity();
} catch (error) {
throw error;
}
@@ -90,13 +90,13 @@ export class PermissionController {
if (oldPermission.id != permission.id) {
throw new PermissionIdsNotMatchingError();
}
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] });
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: await permission.getPrincipal() }, { relations: ['principal'] });
if (existingPermission) {
await this.remove(permission.id, true);
return new ResponsePermission(existingPermission);
}
await this.permissionRepository.save(await permission.updatePermission(oldPermission));
await this.permissionRepository.save(await permission.update(oldPermission));
return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
}
+121
View File
@@ -0,0 +1,121 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors';
import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard';
import { UpdateRunnerCard } from '../models/actions/update/UpdateRunnerCard';
import { RunnerCard } from '../models/entities/RunnerCard';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard';
import { ScanController } from './ScanController';
@JsonController('/cards')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerCardController {
private cardRepository: Repository<RunnerCard>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.cardRepository = getConnectionManager().get().getRepository(RunnerCard);
}
@Get()
@Authorized("CARD:GET")
@ResponseSchema(ResponseRunnerCard, { isArray: true })
@OpenAPI({ description: 'Lists all card.' })
async getAll() {
let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>();
const cards = await this.cardRepository.find({ relations: ['runner', 'runner.group', 'runner.group.parentGroup'] });
cards.forEach(card => {
responseCards.push(new ResponseRunnerCard(card));
});
return responseCards;
}
@Get('/:id')
@Authorized("CARD:GET")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerCardNotFoundError)
@OpenAPI({ description: "Lists all information about the card whose id got provided." })
async getOne(@Param('id') id: number) {
let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] });
if (!card) { throw new RunnerCardNotFoundError(); }
return card.toResponse();
}
@Post('/bulk')
@Authorized("CARD:CREATE")
@ResponseSchema(ResponseEmpty, { statusCode: 200 })
@OpenAPI({ description: "Create blank cards in bulk. <br> Just provide the count as a query param and wait for the 200 response." })
async postBlancoBulk(@QueryParam("count") count: number) {
let createPromises = new Array<any>();
for (let index = 0; index < count; index++) {
createPromises.push(this.cardRepository.save({ runner: null, enabled: true }))
}
await Promise.all(createPromises);
let response = new ResponseEmpty();
response.response = `Created ${count} new blanco cards.`
return response;
}
@Post()
@Authorized("CARD:CREATE")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: "Create a new card. <br> You can provide a associated runner by id but you don't have to." })
async post(@Body({ validate: true }) createCard: CreateRunnerCard) {
let card = await createCard.toEntity();
card = await this.cardRepository.save(card);
return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse();
}
@Put('/:id')
@Authorized("CARD:UPDATE")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the card whose id you provided. <br> Scans created via this card will still be associated with the old runner. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) {
let oldCard = await this.cardRepository.findOne({ id: id });
if (!oldCard) {
throw new RunnerCardNotFoundError();
}
if (oldCard.id != card.id) {
throw new RunnerCardIdsNotMatchingError();
}
await this.cardRepository.save(await card.update(oldCard));
return (await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse();
}
@Delete('/:id')
@Authorized("CARD:DELETE")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerCardHasScansError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: "Delete the card whose id you provided. <br> If no card with this id exists it will just return 204(no content). <br> If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let card = await this.cardRepository.findOne({ id: id });
if (!card) { return null; }
const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans;
if (cardScans.length != 0 && !force) {
throw new RunnerCardHasScansError();
}
const scanController = new ScanController;
for (let scan of cardScans) {
await scanController.remove(scan.id, force);
}
await this.cardRepository.delete(card);
return card.toResponse();
}
}
+63 -11
View File
@@ -1,13 +1,18 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { CreateRunner } from '../models/actions/CreateRunner';
import { UpdateRunner } from '../models/actions/UpdateRunner';
import { CreateRunner } from '../models/actions/create/CreateRunner';
import { UpdateRunner } from '../models/actions/update/UpdateRunner';
import { Runner } from '../models/entities/Runner';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunner } from '../models/responses/ResponseRunner';
import { ResponseScan } from '../models/responses/ResponseScan';
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
import { DonationController } from './DonationController';
import { RunnerCardController } from './RunnerCardController';
import { ScanController } from './ScanController';
@JsonController('/runners')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
@@ -27,7 +32,7 @@ export class RunnerController {
@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
async getAll() {
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] });
const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] });
runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner));
});
@@ -41,11 +46,36 @@ export class RunnerController {
@OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
async getOne(@Param('id') id: number) {
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] })
if (!runner) { throw new RunnerNotFoundError(); }
return new ResponseRunner(runner);
}
@Get('/:id/scans')
@Authorized(["RUNNER:GET", "SCAN:GET"])
@ResponseSchema(ResponseScan, { isArray: true })
@ResponseSchema(ResponseTrackScan, { isArray: true })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Lists all scans of the runner whose id got provided. <br> If you only want the valid scans just add the ?onlyValid=true query param.' })
async getScans(@Param('id') id: number, onlyValid?: boolean) {
let responseScans: ResponseScan[] = new Array<ResponseScan>();
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'scans.track', 'scans.station', 'scans.runner'] })
if (!runner) { throw new RunnerNotFoundError(); }
if (!onlyValid) {
for (let scan of runner.scans) {
responseScans.push(scan.toResponse());
}
}
else {
for (let scan of runner.validScans) {
responseScans.push(scan.toResponse());
}
}
return responseScans;
}
@Post()
@Authorized("RUNNER:CREATE")
@ResponseSchema(ResponseRunner)
@@ -55,13 +85,13 @@ export class RunnerController {
async post(@Body({ validate: true }) createRunner: CreateRunner) {
let runner;
try {
runner = await createRunner.toRunner();
runner = await createRunner.toEntity();
} catch (error) {
throw error;
}
runner = await this.runnerRepository.save(runner)
return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }));
return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }));
}
@Put('/:id')
@@ -81,25 +111,47 @@ export class RunnerController {
throw new RunnerIdsNotMatchingError();
}
await this.runnerRepository.save(await runner.updateRunner(oldRunner));
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
await this.runnerRepository.save(await runner.update(oldRunner));
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }));
}
@Delete('/:id')
@Authorized("RUNNER:DELETE")
@ResponseSchema(ResponseRunner)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerHasDistanceDonationsError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> If no runner with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> This will also delete all scans and cards associated with the runner. <br> If no runner with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let runner = await this.runnerRepository.findOne({ id: id });
if (!runner) { return null; }
const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] });
const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] });
if (!runner) {
throw new RunnerNotFoundError();
}
const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations;
if (runnerDonations.length > 0 && !force) {
throw new RunnerHasDistanceDonationsError();
}
const donationController = new DonationController();
for (let donation of runnerDonations) {
await donationController.remove(donation.id, force);
}
const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards;
const cardController = new RunnerCardController;
for (let card of runnerCards) {
await cardController.remove(card.id, force);
}
const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans;
const scanController = new ScanController;
for (let scan of runnerScans) {
await scanController.remove(scan.id, force);
}
await this.runnerRepository.delete(runner);
return new ResponseRunner(responseRunner);
}
@@ -1,127 +0,0 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
import { UpdateRunnerOrganisation } from '../models/actions/UpdateRunnerOrganisation';
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation';
import { RunnerController } from './RunnerController';
import { RunnerTeamController } from './RunnerTeamController';
@JsonController('/organisations')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerOrganisationController {
private runnerOrganisationRepository: Repository<RunnerOrganisation>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.runnerOrganisationRepository = getConnectionManager().get().getRepository(RunnerOrganisation);
}
@Get()
@Authorized("ORGANISATION:GET")
@ResponseSchema(ResponseRunnerOrganisation, { isArray: true })
@OpenAPI({ description: 'Lists all organisations. <br> This includes their address, contact and teams (if existing/associated).' })
async getAll() {
let responseTeams: ResponseRunnerOrganisation[] = new Array<ResponseRunnerOrganisation>();
const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] });
runners.forEach(runner => {
responseTeams.push(new ResponseRunnerOrganisation(runner));
});
return responseTeams;
}
@Get('/:id')
@Authorized("ORGANISATION:GET")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerOrganisationNotFoundError)
@OpenAPI({ description: 'Lists all information about the organisation whose id got provided.' })
async getOne(@Param('id') id: number) {
let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] });
if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); }
return new ResponseRunnerOrganisation(runnerOrg);
}
@Post()
@Authorized("ORGANISATION:CREATE")
@ResponseSchema(ResponseRunnerOrganisation)
@OpenAPI({ description: 'Create a new organsisation.' })
async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) {
let runnerOrganisation;
try {
runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation();
} catch (error) {
throw error;
}
runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation);
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }));
}
@Put('/:id')
@Authorized("ORGANISATION:UPDATE")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the organisation whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) updateOrganisation: UpdateRunnerOrganisation) {
let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!oldRunnerOrganisation) {
throw new RunnerOrganisationNotFoundError();
}
if (oldRunnerOrganisation.id != updateOrganisation.id) {
throw new RunnerOrganisationIdsNotMatchingError();
}
await this.runnerOrganisationRepository.save(await updateOrganisation.updateRunnerOrganisation(oldRunnerOrganisation));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] }));
}
@Delete('/:id')
@Authorized("ORGANISATION:DELETE")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organisation still has runners and/or teams associated this will fail. <br> To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> If no organisation with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!organisation) { return null; }
let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] });
if (!force) {
if (runnerOrganisation.teams.length != 0) {
throw new RunnerOrganisationHasTeamsError();
}
}
const teamController = new RunnerTeamController()
for (let team of runnerOrganisation.teams) {
await teamController.remove(team.id, true);
}
if (!force) {
if (runnerOrganisation.runners.length != 0) {
throw new RunnerOrganisationHasRunnersError();
}
}
const runnerController = new RunnerController()
for (let runner of runnerOrganisation.runners) {
await runnerController.remove(runner.id, true);
}
const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation);
await this.runnerOrganisationRepository.delete(organisation);
return responseOrganisation;
}
}
@@ -0,0 +1,145 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerOrganizationHasRunnersError, RunnerOrganizationHasTeamsError, RunnerOrganizationIdsNotMatchingError, RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization';
import { UpdateRunnerOrganization } from '../models/actions/update/UpdateRunnerOrganization';
import { Runner } from '../models/entities/Runner';
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunner } from '../models/responses/ResponseRunner';
import { ResponseRunnerOrganization } from '../models/responses/ResponseRunnerOrganization';
import { RunnerController } from './RunnerController';
import { RunnerTeamController } from './RunnerTeamController';
@JsonController('/organizations')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerOrganizationController {
private runnerOrganizationRepository: Repository<RunnerOrganization>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.runnerOrganizationRepository = getConnectionManager().get().getRepository(RunnerOrganization);
}
@Get()
@Authorized("ORGANIZATION:GET")
@ResponseSchema(ResponseRunnerOrganization, { isArray: true })
@OpenAPI({ description: 'Lists all organizations. <br> This includes their address, contact and teams (if existing/associated).' })
async getAll() {
let responseTeams: ResponseRunnerOrganization[] = new Array<ResponseRunnerOrganization>();
const runners = await this.runnerOrganizationRepository.find({ relations: ['contact', 'teams'] });
runners.forEach(runner => {
responseTeams.push(new ResponseRunnerOrganization(runner));
});
return responseTeams;
}
@Get('/:id')
@Authorized("ORGANIZATION:GET")
@ResponseSchema(ResponseRunnerOrganization)
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerOrganizationNotFoundError)
@OpenAPI({ description: 'Lists all information about the organization whose id got provided.' })
async getOne(@Param('id') id: number) {
let runnerOrg = await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['contact', 'teams'] });
if (!runnerOrg) { throw new RunnerOrganizationNotFoundError(); }
return new ResponseRunnerOrganization(runnerOrg);
}
@Get('/:id/runners')
@Authorized(["RUNNER:GET", "SCAN:GET"])
@ResponseSchema(ResponseRunner, { isArray: true })
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Lists all runners from this org and it\'s teams (if you don\'t provide the ?onlyDirect=true param). <br> This includes the runner\'s group and distance ran.' })
async getRunners(@Param('id') id: number, @QueryParam('onlyDirect') onlyDirect: boolean) {
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
let runners: Runner[];
if (!onlyDirect) { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.group', 'teams.runners.group.parentGroup', 'teams.runners.scans', 'teams.runners.scans.track'] })).allRunners; }
else { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners; }
runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner));
});
return responseRunners;
}
@Post()
@Authorized("ORGANIZATION:CREATE")
@ResponseSchema(ResponseRunnerOrganization)
@OpenAPI({ description: 'Create a new organsisation.' })
async post(@Body({ validate: true }) createRunnerOrganization: CreateRunnerOrganization) {
let runnerOrganization;
try {
runnerOrganization = await createRunnerOrganization.toEntity();
} catch (error) {
throw error;
}
runnerOrganization = await this.runnerOrganizationRepository.save(runnerOrganization);
return new ResponseRunnerOrganization(await this.runnerOrganizationRepository.findOne(runnerOrganization, { relations: ['contact', 'teams'] }));
}
@Put('/:id')
@Authorized("ORGANIZATION:UPDATE")
@ResponseSchema(ResponseRunnerOrganization)
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerOrganizationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the organization whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) updateOrganization: UpdateRunnerOrganization) {
let oldRunnerOrganization = await this.runnerOrganizationRepository.findOne({ id: id });
if (!oldRunnerOrganization) {
throw new RunnerOrganizationNotFoundError();
}
if (oldRunnerOrganization.id != updateOrganization.id) {
throw new RunnerOrganizationIdsNotMatchingError();
}
await this.runnerOrganizationRepository.save(await updateOrganization.update(oldRunnerOrganization));
return new ResponseRunnerOrganization(await this.runnerOrganizationRepository.findOne(id, { relations: ['contact', 'teams'] }));
}
@Delete('/:id')
@Authorized("ORGANIZATION:DELETE")
@ResponseSchema(ResponseRunnerOrganization)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerOrganizationHasTeamsError, { statusCode: 406 })
@ResponseSchema(RunnerOrganizationHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organization still has runners and/or teams associated this will fail. <br> To delete the organization with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact. <br> If no organization with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let organization = await this.runnerOrganizationRepository.findOne({ id: id });
if (!organization) { return null; }
let runnerOrganization = await this.runnerOrganizationRepository.findOne(organization, { relations: ['contact', 'runners', 'teams'] });
if (!force) {
if (runnerOrganization.teams.length != 0) {
throw new RunnerOrganizationHasTeamsError();
}
}
const teamController = new RunnerTeamController()
for (let team of runnerOrganization.teams) {
await teamController.remove(team.id, true);
}
if (!force) {
if (runnerOrganization.runners.length != 0) {
throw new RunnerOrganizationHasRunnersError();
}
}
const runnerController = new RunnerController()
for (let runner of runnerOrganization.runners) {
await runnerController.remove(runner.id, true);
}
const responseOrganization = new ResponseRunnerOrganization(runnerOrganization);
await this.runnerOrganizationRepository.delete(organization);
return responseOrganization;
}
}
@@ -0,0 +1,186 @@
import { Request } from "express";
import * as jwt from "jsonwebtoken";
import { Body, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { config } from '../config';
import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError';
import { MailSendingError } from '../errors/MailErrors';
import { RunnerEmailNeededError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors';
import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { JwtCreator } from '../jwtcreator';
import { Mailer } from '../mailer';
import ScanAuth from '../middlewares/ScanAuth';
import { CreateSelfServiceCitizenRunner } from '../models/actions/create/CreateSelfServiceCitizenRunner';
import { CreateSelfServiceRunner } from '../models/actions/create/CreateSelfServiceRunner';
import { Runner } from '../models/entities/Runner';
import { RunnerGroup } from '../models/entities/RunnerGroup';
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
import { ScanStation } from '../models/entities/ScanStation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation';
import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan';
@JsonController()
export class RunnerSelfServiceController {
private runnerRepository: Repository<Runner>;
private orgRepository: Repository<RunnerOrganization>;
private stationRepository: Repository<ScanStation>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.runnerRepository = getConnectionManager().get().getRepository(Runner);
this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization);
this.stationRepository = getConnectionManager().get().getRepository(ScanStation);
}
@Get('/runners/me/:jwt')
@ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' })
async get(@Param('jwt') token: string) {
return (new ResponseSelfServiceRunner(await this.getRunner(token)));
}
@Get('/runners/me/:jwt/scans')
@ResponseSchema(ResponseSelfServiceScan, { isArray: true })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all your (runner) scans. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' })
async getScans(@Param('jwt') token: string) {
const scans = (await this.getRunner(token)).scans;
let responseScans = new Array<ResponseSelfServiceScan>()
for (let scan of scans) {
responseScans.push(new ResponseSelfServiceScan(scan));
}
return responseScans;
}
@Get('/stations/me')
@UseBefore(ScanAuth)
@ResponseSchema(ResponseScanStation)
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@OnUndefined(ScanStationNotFoundError)
@OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] })
async getStationMe(@Req() req: Request) {
let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] })
if (!scan) { throw new ScanStationNotFoundError(); }
return scan.toResponse();
}
@Post('/runners/forgot')
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(ResponseEmpty)
@OpenAPI({ description: 'Use this endpoint to reuqest a new selfservice token/link to be sent to your mail address (rate limited to one mail every 24hrs).' })
async requestNewToken(@QueryParam('mail') mail: string) {
if (!mail) {
throw new RunnerNotFoundError();
}
const runner = await this.runnerRepository.findOne({ email: mail });
if (!runner) { throw new RunnerNotFoundError(); }
if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 60 * 24)) { throw new RunnerSelfserviceTimeoutError(); }
const token = JwtCreator.createSelfService(runner);
try {
await Mailer.sendSelfserviceForgottenMail(runner.email, token, "en")
} catch (error) {
throw new MailSendingError();
}
runner.resetRequestedTimestamp = Math.floor(Date.now() / 1000);
await this.runnerRepository.save(runner);
return { token };
}
@Post('/runners/register')
@ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerEmailNeededError, { statusCode: 406 })
@OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.' })
async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner) {
let runner = await createRunner.toEntity();
runner = await this.runnerRepository.save(runner);
let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }));
response.token = JwtCreator.createSelfService(runner);
try {
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en")
} catch (error) {
throw new MailSendingError();
}
return response;
}
@Post('/runners/register/:token')
@ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.' })
async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner) {
const org = await this.getOrgansisation(token);
let runner = await createRunner.toEntity(org);
runner = await this.runnerRepository.save(runner);
let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }));
response.token = JwtCreator.createSelfService(runner);
try {
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en")
} catch (error) {
throw new MailSendingError();
}
return response;
}
@Get('/organizations/selfservice/:token')
@ResponseSchema(ResponseSelfServiceOrganisation, { isArray: false })
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Get the basic info and teams for a org.' })
async getSelfserviceOrg(@Param('token') token: string) {
const orgid = (await this.getOrgansisation(token)).id;
const org = await this.orgRepository.findOne({ id: orgid }, { relations: ['teams'] })
return new ResponseSelfServiceOrganisation(<RunnerOrganization>org);
}
/**
* Get's a runner by a provided jwt token.
* @param token The runner jwt provided by the runner to identitfy themselves.
*/
private async getRunner(token: string): Promise<Runner> {
if (token == "") { throw new JwtNotProvidedError(); }
let jwtPayload = undefined
try {
jwtPayload = <any>jwt.verify(token, config.jwt_secret);
} catch (error) {
throw new InvalidCredentialsError();
}
const runner = await this.runnerRepository.findOne({ id: jwtPayload["id"] }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] });
if (!runner) { throw new RunnerNotFoundError() }
return runner;
}
/**
* Get's a runner org by a provided registration api key.
* @param token The organization's registration api token.
*/
private async getOrgansisation(token: string): Promise<RunnerGroup> {
token = Buffer.from(token, 'base64').toString('utf8');
const organization = await this.orgRepository.findOne({ key: token });
if (!organization) { throw new RunnerOrganizationNotFoundError; }
return organization;
}
}
+21 -6
View File
@@ -2,10 +2,11 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
import { UpdateRunnerTeam } from '../models/actions/UpdateRunnerTeam';
import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam';
import { RunnerTeam } from '../models/entities/RunnerTeam';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunner } from '../models/responses/ResponseRunner';
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
import { RunnerController } from './RunnerController';
@@ -25,7 +26,7 @@ export class RunnerTeamController {
@Get()
@Authorized("TEAM:GET")
@ResponseSchema(ResponseRunnerTeam, { isArray: true })
@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organisation and contact (if existing/associated).' })
@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organization and contact (if existing/associated).' })
async getAll() {
let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>();
const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
@@ -47,6 +48,20 @@ export class RunnerTeamController {
return new ResponseRunnerTeam(runnerTeam);
}
@Get('/:id/runners')
@Authorized(["RUNNER:GET", "SCAN:GET"])
@ResponseSchema(ResponseRunner, { isArray: true })
@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Lists all runners from this team. <br> This includes the runner\'s group and distance ran.' })
async getRunners(@Param('id') id: number) {
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
const runners = (await this.runnerTeamRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners;
runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner));
});
return responseRunners;
}
@Post()
@Authorized("TEAM:CREATE")
@ResponseSchema(ResponseRunnerTeam)
@@ -54,7 +69,7 @@ export class RunnerTeamController {
async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
let runnerTeam;
try {
runnerTeam = await createRunnerTeam.toRunnerTeam();
runnerTeam = await createRunnerTeam.toEntity();
} catch (error) {
throw error;
}
@@ -82,7 +97,7 @@ export class RunnerTeamController {
throw new RunnerTeamIdsNotMatchingError();
}
await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam));
await this.runnerTeamRepository.save(await runnerTeam.update(oldRunnerTeam));
return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
}
@@ -93,7 +108,7 @@ export class RunnerTeamController {
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> If no team with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact.<br> If no team with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let team = await this.runnerTeamRepository.findOne({ id: id });
if (!team) { return null; }
+144
View File
@@ -0,0 +1,144 @@
import { Request } from "express";
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors';
import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
import ScanAuth from '../middlewares/ScanAuth';
import { CreateScan } from '../models/actions/create/CreateScan';
import { CreateTrackScan } from '../models/actions/create/CreateTrackScan';
import { UpdateScan } from '../models/actions/update/UpdateScan';
import { UpdateTrackScan } from '../models/actions/update/UpdateTrackScan';
import { Scan } from '../models/entities/Scan';
import { TrackScan } from '../models/entities/TrackScan';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScan } from '../models/responses/ResponseScan';
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
@JsonController('/scans')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ScanController {
private scanRepository: Repository<Scan>;
private trackScanRepository: Repository<TrackScan>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.scanRepository = getConnectionManager().get().getRepository(Scan);
this.trackScanRepository = getConnectionManager().get().getRepository(TrackScan);
}
@Get()
@Authorized("SCAN:GET")
@ResponseSchema(ResponseScan, { isArray: true })
@ResponseSchema(ResponseTrackScan, { isArray: true })
@OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' })
async getAll() {
let responseScans: ResponseScan[] = new Array<ResponseScan>();
const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] });
scans.forEach(scan => {
responseScans.push(scan.toResponse());
});
return responseScans;
}
@Get('/:id')
@Authorized("SCAN:GET")
@ResponseSchema(ResponseScan)
@ResponseSchema(ResponseTrackScan)
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
@OnUndefined(ScanNotFoundError)
@OpenAPI({ description: 'Lists all information about the scan whose id got provided. This includes the scan\'s runner\'s distance ran.' })
async getOne(@Param('id') id: number) {
let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })
if (!scan) { throw new ScanNotFoundError(); }
return scan.toResponse();
}
@Post()
@UseBefore(ScanAuth)
@ResponseSchema(ResponseScan)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new scan (not track scan - use /scans/trackscans instead). <br> Please rmemember to provide the scan\'s runner\'s id and distance.', security: [{ "StationApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async post(@Body({ validate: true }) createScan: CreateScan) {
let scan = await createScan.toEntity();
scan = await this.scanRepository.save(scan);
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Post("/trackscans")
@UseBefore(ScanAuth)
@ResponseSchema(ResponseTrackScan)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new track scan (for "normal" scans use /scans instead). <br> Please remember that to provide the scan\'s card\'s station\'s id.', security: [{ "StationApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan, @Req() req: Request) {
const station_id = req.headers["station_id"];
if (station_id) {
createScan.station = parseInt(station_id.toString());
}
let scan = await createScan.toEntity();
scan = await this.trackScanRepository.save(scan);
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Put('/:id')
@Authorized("SCAN:UPDATE")
@ResponseSchema(ResponseScan)
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the scan (not track scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that ids can't be changed and distances must be positive." })
async put(@Param('id') id: number, @Body({ validate: true }) scan: UpdateScan) {
let oldScan = await this.scanRepository.findOne({ id: id });
if (!oldScan) {
throw new ScanNotFoundError();
}
if (oldScan.id != scan.id) {
throw new ScanIdsNotMatchingError();
}
await this.scanRepository.save(await scan.update(oldScan));
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Put('/trackscans/:id')
@Authorized("SCAN:UPDATE")
@ResponseSchema(ResponseTrackScan)
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: 'Update the track scan (not "normal" scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that only the validity, runner and track can be changed.' })
async putTrackScan(@Param('id') id: number, @Body({ validate: true }) scan: UpdateTrackScan) {
let oldScan = await this.trackScanRepository.findOne({ id: id });
if (!oldScan) {
throw new ScanNotFoundError();
}
if (oldScan.id != scan.id) {
throw new ScanIdsNotMatchingError();
}
await this.trackScanRepository.save(await scan.update(oldScan));
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Delete('/:id')
@Authorized("SCAN:DELETE")
@ResponseSchema(ResponseScan)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the scan whose id you provided. <br> If no scan with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let scan = await this.scanRepository.findOne({ id: id });
if (!scan) { return null; }
const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] });
await this.scanRepository.delete(scan);
return responseScan.toResponse();
}
}
+108
View File
@@ -0,0 +1,108 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { TrackNotFoundError } from '../errors/TrackErrors';
import { CreateScanStation } from '../models/actions/create/CreateScanStation';
import { UpdateScanStation } from '../models/actions/update/UpdateScanStation';
import { ScanStation } from '../models/entities/ScanStation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
import { ScanController } from './ScanController';
@JsonController('/stations')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ScanStationController {
private stationRepository: Repository<ScanStation>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.stationRepository = getConnectionManager().get().getRepository(ScanStation);
}
@Get()
@Authorized("STATION:GET")
@ResponseSchema(ResponseScanStation, { isArray: true })
@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' })
async getAll() {
let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>();
const stations = await this.stationRepository.find({ relations: ['track'] });
stations.forEach(station => {
responseStations.push(station.toResponse());
});
return responseStations;
}
@Get('/:id')
@Authorized("STATION:GET")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@OnUndefined(ScanStationNotFoundError)
@OpenAPI({ description: 'Lists all information about the station whose id got provided. <br> This includes it\'s associated track.' })
async getOne(@Param('id') id: number) {
let scan = await this.stationRepository.findOne({ id: id }, { relations: ['track'] })
if (!scan) { throw new ScanStationNotFoundError(); }
return scan.toResponse();
}
@Post()
@Authorized("STATION:CREATE")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new station. <br> Please remeber to provide the station\'s track\'s id. <br> Please also remember that the station key is only visibe on creation.' })
async post(@Body({ validate: true }) createStation: CreateScanStation) {
let newStation = await createStation.toEntity();
const station = await this.stationRepository.save(newStation);
let responseStation = (await this.stationRepository.findOne({ id: station.id }, { relations: ['track'] })).toResponse();
responseStation.key = newStation.cleartextkey;
return responseStation;
}
@Put('/:id')
@Authorized("STATION:UPDATE")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanStationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the station whose id you provided. <br> Please remember that only the description and enabled state can be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) station: UpdateScanStation) {
let oldStation = await this.stationRepository.findOne({ id: id });
if (!oldStation) {
throw new ScanStationNotFoundError();
}
if (oldStation.id != station.id) {
throw new ScanStationIdsNotMatchingError();
}
await this.stationRepository.save(await station.update(oldStation));
return (await this.stationRepository.findOne({ id: id }, { relations: ['track'] })).toResponse();
}
@Delete('/:id')
@Authorized("STATION:DELETE")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(ScanStationHasScansError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the station whose id you provided. <br> If no station with this id exists it will just return 204(no content). <br> If the station still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with/created by this station - please disable it instead).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let station = await this.stationRepository.findOne({ id: id });
if (!station) { return null; }
const stationScans = (await this.stationRepository.findOne({ id: station.id }, { relations: ["scans"] })).scans;
if (stationScans.length != 0 && !force) {
throw new ScanStationHasScansError();
}
const scanController = new ScanController;
for (let scan of stationScans) {
await scanController.remove(scan.id, force);
}
const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] });
await this.stationRepository.delete(station);
return responseStation.toResponse();
}
}
+4 -4
View File
@@ -1,9 +1,9 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { StatsClientNotFoundError } from '../errors/StatsClientErrors';
import { TrackNotFoundError } from "../errors/TrackErrors";
import { CreateStatsClient } from '../models/actions/CreateStatsClient';
import { CreateStatsClient } from '../models/actions/create/CreateStatsClient';
import { StatsClient } from '../models/entities/StatsClient';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseStatsClient } from '../models/responses/ResponseStatsClient';
@@ -53,7 +53,7 @@ export class StatsClientController {
@Body({ validate: true })
client: CreateStatsClient
) {
let newClient = await this.clientRepository.save(await client.toStatsClient());
let newClient = await this.clientRepository.save(await client.toEntity());
let responseClient = new ResponseStatsClient(newClient);
responseClient.key = newClient.cleartextkey;
return responseClient;
@@ -65,7 +65,7 @@ export class StatsClientController {
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: "Delete the stats client whose id you provided. <br> If no client with this id exists it will just return 204(no content)." })
async remove(@Param("id") id: number) {
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let client = await this.clientRepository.findOne({ id: id });
if (!client) { return null; }
+9 -9
View File
@@ -4,12 +4,12 @@ import { getConnection } from 'typeorm';
import StatsAuth from '../middlewares/StatsAuth';
import { Donation } from '../models/entities/Donation';
import { Runner } from '../models/entities/Runner';
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
import { RunnerTeam } from '../models/entities/RunnerTeam';
import { Scan } from '../models/entities/Scan';
import { User } from '../models/entities/User';
import { ResponseStats } from '../models/responses/ResponseStats';
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganisation';
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization';
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
@@ -23,7 +23,7 @@ export class StatsController {
let connection = getConnection();
let runners = await connection.getRepository(Runner).find({ relations: ['scans', 'scans.track'] });
let teams = await connection.getRepository(RunnerTeam).find();
let orgs = await connection.getRepository(RunnerOrganisation).find();
let orgs = await connection.getRepository(RunnerOrganization).find();
let users = await connection.getRepository(User).find();
let scans = await connection.getRepository(Scan).find();
let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
@@ -94,12 +94,12 @@ export class StatsController {
return responseTeams;
}
@Get("/organisations/distance")
@Get("/organizations/distance")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organisations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
@OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopOrgsByDistance() {
let orgs = await getConnection().getRepository(RunnerOrganisation).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
let topOrgs = orgs.sort((org1, org2) => org1.distance - org2.distance).slice(0, 9);
let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
topOrgs.forEach(org => {
@@ -108,12 +108,12 @@ export class StatsController {
return responseOrgs;
}
@Get("/organisations/donations")
@Get("/organizations/donations")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organisations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
@OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopOrgsByDonations() {
let orgs = await getConnection().getRepository(RunnerOrganisation).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
let topOrgs = orgs.sort((org1, org2) => org1.distanceDonationAmount - org2.distanceDonationAmount).slice(0, 9);
let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
topOrgs.forEach(org => {
+11 -2
View File
@@ -1,11 +1,12 @@
import { Get, JsonController } from 'routing-controllers';
import { OpenAPI } from 'routing-controllers-openapi';
import { getConnection } from 'typeorm';
import { config } from '../config';
@JsonController('/status')
@JsonController()
export class StatusController {
@Get()
@Get('/status')
@OpenAPI({ description: "A very basic status/health endpoint that just checks if the database connection is available. <br> The available information depth will be expanded later." })
get() {
let connection;
@@ -19,4 +20,12 @@ export class StatusController {
"database connection": "✔"
};
}
@Get('/version')
@OpenAPI({ description: "A very basic endpoint that just returns the curent package version." })
getVersion() {
return {
"version": config.version
}
}
}
+17 -7
View File
@@ -1,12 +1,13 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors";
import { CreateTrack } from '../models/actions/CreateTrack';
import { UpdateTrack } from '../models/actions/UpdateTrack';
import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors";
import { CreateTrack } from '../models/actions/create/CreateTrack';
import { UpdateTrack } from '../models/actions/update/UpdateTrack';
import { Track } from '../models/entities/Track';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseTrack } from '../models/responses/ResponseTrack';
import { ScanStationController } from './ScanStationController';
@JsonController('/tracks')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
@@ -54,7 +55,7 @@ export class TrackController {
@Body({ validate: true })
track: CreateTrack
) {
return new ResponseTrack(await this.trackRepository.save(track.toTrack()));
return new ResponseTrack(await this.trackRepository.save(await track.toEntity()));
}
@Put('/:id')
@@ -74,7 +75,7 @@ export class TrackController {
if (oldTrack.id != updateTrack.id) {
throw new TrackIdsNotMatchingError();
}
await this.trackRepository.save(await updateTrack.updateTrack(oldTrack));
await this.trackRepository.save(await updateTrack.update(oldTrack));
return new ResponseTrack(await this.trackRepository.findOne({ id: id }));
}
@@ -85,10 +86,19 @@ export class TrackController {
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: "Delete the track whose id you provided. <br> If no track with this id exists it will just return 204(no content)." })
async remove(@Param("id") id: number) {
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let track = await this.trackRepository.findOne({ id: id });
if (!track) { return null; }
const trackStations = (await this.trackRepository.findOne({ id: id }, { relations: ["stations"] })).stations;
if (trackStations.length != 0 && !force) {
throw new TrackHasScanStationsError();
}
const stationController = new ScanStationController;
for (let station of trackStations) {
await stationController.remove(station.id, force);
}
await this.trackRepository.delete(track);
return new ResponseTrack(track);
}
+36 -19
View File
@@ -1,13 +1,14 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUser } from '../models/actions/CreateUser';
import { UpdateUser } from '../models/actions/UpdateUser';
import { CreateUser } from '../models/actions/create/CreateUser';
import { UpdateUser } from '../models/actions/update/UpdateUser';
import { User } from '../models/entities/User';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseUser } from '../models/responses/ResponseUser';
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
import { PermissionController } from './PermissionController';
@@ -25,11 +26,11 @@ export class UserController {
@Get()
@Authorized("USER:GET")
@ResponseSchema(User, { isArray: true })
@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions directly granted to them (if existing/associated).' })
@ResponseSchema(ResponseUser, { isArray: true })
@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions granted to them.' })
async getAll() {
let responseUsers: ResponseUser[] = new Array<ResponseUser>();
const users = await this.userRepository.find({ relations: ['permissions', 'groups'] });
const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] });
users.forEach(user => {
responseUsers.push(new ResponseUser(user));
});
@@ -38,38 +39,52 @@ export class UserController {
@Get('/:id')
@Authorized("USER:GET")
@ResponseSchema(User)
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that only permissions granted directly to the user will show up here, not permissions inherited from groups.' })
@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that all permissions granted to the user will show up here.' })
async getOne(@Param('id') id: number) {
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUser(user);
}
@Get('/:id/permissions')
@Authorized("USER:GET")
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all permissions granted to the user sorted into directly granted and inherited as permission response objects.' })
async getPermissions(@Param('id') id: number) {
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUserPermissions(user);
}
@Post()
@Authorized("USER:CREATE")
@ResponseSchema(User)
@ResponseSchema(UserGroupNotFoundError)
@ResponseSchema(ResponseUser)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: 'Create a new user. <br> If you want to grant permissions to the user you have to create them seperately by posting to /api/permissions after creating the user.' })
async post(@Body({ validate: true }) createUser: CreateUser) {
let user;
try {
user = await createUser.toUser();
user = await createUser.toEntity();
} catch (error) {
throw error;
}
user = await this.userRepository.save(user)
return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups'] }));
return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Put('/:id')
@Authorized("USER:UPDATE")
@ResponseSchema(User)
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: "Update the user whose id you provided. <br> To change the permissions directly granted to the user please use /api/permissions instead. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: id });
@@ -81,21 +96,23 @@ export class UserController {
if (oldUser.id != updateUser.id) {
throw new UserIdsNotMatchingError();
}
await this.userRepository.save(await updateUser.updateUser(oldUser));
await this.userRepository.save(await updateUser.update(oldUser));
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }));
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Delete('/:id')
@Authorized("USER:DELETE")
@ResponseSchema(User)
@ResponseSchema(ResponseUser)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the user whose id you provided. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the user whose id you provided. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
if (!force) { throw new UserDeletionNotConfirmedError; }
let user = await this.userRepository.findOne({ id: id });
if (!user) { return null; }
const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] });;
const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
const permissionControler = new PermissionController();
for (let permission of responseUser.permissions) {
+39 -20
View File
@@ -1,12 +1,13 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
import { CreateUserGroup } from '../models/actions/create/CreateUserGroup';
import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup';
import { UserGroup } from '../models/entities/UserGroup';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseUserGroup } from '../models/responses/ResponseUserGroup';
import { ResponseUserGroupPermissions } from '../models/responses/ResponseUserGroupPermissions';
import { PermissionController } from './PermissionController';
@@ -24,20 +25,37 @@ export class UserGroupController {
@Get()
@Authorized("USERGROUP:GET")
@ResponseSchema(UserGroup, { isArray: true })
@ResponseSchema(ResponseUserGroup, { isArray: true })
@OpenAPI({ description: 'Lists all groups. <br> The information provided might change while the project continues to evolve.' })
getAll() {
return this.userGroupsRepository.find({ relations: ["permissions"] });
async getAll() {
let responseGroups: ResponseUserGroup[] = new Array<ResponseUserGroup>();
const groups = await this.userGroupsRepository.find({ relations: ['permissions'] });
groups.forEach(group => {
responseGroups.push(group.toResponse());
});
return responseGroups;
}
@Get('/:id')
@Authorized("USERGROUP:GET")
@ResponseSchema(UserGroup)
@ResponseSchema(ResponseUserGroup)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@OnUndefined(UserGroupNotFoundError)
@OpenAPI({ description: 'Lists all information about the group whose id got provided. <br> The information provided might change while the project continues to evolve.' })
getOne(@Param('id') id: number) {
return this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
async getOne(@Param('id') id: number) {
return await (await (this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] }))).toResponse();
}
@Get('/:id/permissions')
@Authorized("USERGROUP:GET")
@ResponseSchema(ResponseUserGroupPermissions)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@OnUndefined(UserGroupNotFoundError)
@OpenAPI({ description: 'Lists all permissions granted to the group as permission response objects.' })
async getPermissions(@Param('id') id: number) {
let group = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions', 'permissions.principal'] })
if (!group) { throw new UserGroupNotFoundError(); }
return new ResponseUserGroupPermissions(group);
}
@Post()
@@ -48,12 +66,13 @@ export class UserGroupController {
async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
let userGroup;
try {
userGroup = await createUserGroup.toUserGroup();
userGroup = await createUserGroup.toEntity();
} catch (error) {
throw error;
}
return this.userGroupsRepository.save(userGroup);
userGroup = await this.userGroupsRepository.save(userGroup);
return (await (this.userGroupsRepository.findOne({ id: userGroup.id }, { relations: ["permissions"] }))).toResponse();
}
@Put('/:id')
@@ -62,19 +81,19 @@ export class UserGroupController {
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the group whose id you provided. <br> To change the permissions granted to the group please use /api/permissions instead. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) {
let oldUserGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
async put(@Param('id') id: number, @Body({ validate: true }) updateGroup: UpdateUserGroup) {
let oldGroup = await this.userGroupsRepository.findOne({ id: id });
if (!oldUserGroup) {
throw new UserGroupNotFoundError()
if (!oldGroup) {
throw new UserGroupNotFoundError();
}
if (oldUserGroup.id != userGroup.id) {
if (oldGroup.id != updateGroup.id) {
throw new UserGroupIdsNotMatchingError();
}
await this.userGroupsRepository.save(await updateGroup.update(oldGroup));
await this.userGroupsRepository.save(userGroup);
return userGroup;
return (await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] })).toResponse();
}
@Delete('/:id')
@@ -84,13 +103,13 @@ export class UserGroupController {
@OnUndefined(204)
@OpenAPI({ description: 'Delete the group whose id you provided. <br> If there are any permissions directly granted to the group they will get deleted as well. <br> Users associated with this group won\'t get deleted - just deassociated. <br> If no group with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let group = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
let group = await this.userGroupsRepository.findOne({ id: id });
if (!group) { return null; }
const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });
const permissionControler = new PermissionController();
const permissionController = new PermissionController();
for (let permission of responseGroup.permissions) {
await permissionControler.remove(permission.id, true);
await permissionController.remove(permission.id, true);
}
await this.userGroupsRepository.delete(group);
+42 -9
View File
@@ -1,24 +1,57 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
import { BadRequestError } from 'routing-controllers';
/**
* Error to throw, when to provided address doesn't belong to the accepted types.
* Error to throw when an address's postal code fails validation.
*/
export class AddressWrongTypeError extends NotAcceptableError {
export class AddressPostalCodeInvalidError extends BadRequestError {
@IsString()
name = "AddressWrongTypeError"
name = "AddressPostalCodeInvalidError"
@IsString()
message = "The address must be an existing adress's id. \n You provided a object of another type."
message = "The postal code you provided is invalid. \n Please check if your postal code follows the postal code validation guidelines."
}
/**
* Error to throw, when a non-existant address get's loaded.
* Error to throw when an non-empty address's first line isn't set.
*/
export class AddressNotFoundError extends NotFoundError {
export class AddressFirstLineEmptyError extends BadRequestError {
@IsString()
name = "AddressNotFoundError"
name = "AddressFirstLineEmptyError"
@IsString()
message = "The address you provided couldn't be located in the system. \n Please check your request."
message = "You provided a empty first address line. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's postal code isn't set.
*/
export class AddressPostalCodeEmptyError extends BadRequestError {
@IsString()
name = "AddressPostalCodeEmptyError"
@IsString()
message = "You provided a empty postal code. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's city isn't set.
*/
export class AddressCityEmptyError extends BadRequestError {
@IsString()
name = "AddressCityEmptyError"
@IsString()
message = "You provided a empty city. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's country isn't set.
*/
export class AddressCountryEmptyError extends BadRequestError {
@IsString()
name = "AddressCountryEmptyError"
@IsString()
message = "You provided a empty country. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
+1 -1
View File
@@ -118,7 +118,7 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError {
}
/**
* Error to throw when someone tryes to reset a user's password more than once in 15 minutes.
* Error to throw when someone tries to reset a user's password more than once in 15 minutes.
*/
export class ResetAlreadyRequestedError extends NotAcceptableError {
@IsString()
+25
View File
@@ -0,0 +1,25 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a Donation couldn't be found.
*/
export class DonationNotFoundError extends NotFoundError {
@IsString()
name = "DonationNotFoundError"
@IsString()
message = "Donation not found!"
}
/**
* Error to throw when two Donations' ids don't match.
* Usually occurs when a user tries to change a Donation's id.
*/
export class DonationIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "DonationIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a Donation's id: This isn't allowed!"
}
+11
View File
@@ -33,4 +33,15 @@ export class DonorReceiptAddressNeededError extends NotAcceptableError {
@IsString()
message = "An address is needed to create a receipt for a donor. \n You didn't provide one."
}
/**
* Error to throw when a donor still has donations associated.
*/
export class DonorHasDonationsError extends NotAcceptableError {
@IsString()
name = "DonorHasDonationsError"
@IsString()
message = "This donor still has donations associated with it. \n If you want to delete this donor with all it's donations and teams add `?force` to your query."
}
+14 -13
View File
@@ -2,18 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw, when a provided groupContact doesn't belong to the accepted types.
*/
export class GroupContactWrongTypeError extends NotAcceptableError {
@IsString()
name = "GroupContactWrongTypeError"
@IsString()
message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type."
}
/**
* Error to throw, when a non-existant groupContact get's loaded.
* Error to throw, when a non-existent contact get's requested.
*/
export class GroupContactNotFoundError extends NotFoundError {
@IsString()
@@ -21,4 +10,16 @@ export class GroupContactNotFoundError extends NotFoundError {
@IsString()
message = "The groupContact you provided couldn't be located in the system. \n Please check your request."
}
}
/**
* Error to throw when two contacts' ids don't match.
* Usually occurs when a user tries to change a contact's id.
*/
export class GroupContactIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "GroupContactIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a contact's id: This isn't allowed!"
}
+17
View File
@@ -0,0 +1,17 @@
import { IsString } from 'class-validator';
import { InternalServerError } from 'routing-controllers';
/**
* Error to throw when a permission couldn't be found.
*/
export class MailSendingError extends InternalServerError {
@IsString()
name = "MailSendingError"
@IsString()
message = "We had a problem sending the mail!"
constructor() {
super("We had a problem sending the mail!");
}
}
+2 -2
View File
@@ -13,12 +13,12 @@ export class PrincipalNotFoundError extends NotFoundError {
}
/**
* Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
* Error to throw, when a provided runner organization doesn't belong to the accepted types.
*/
export class PrincipalWrongTypeError extends NotAcceptableError {
@IsString()
name = "PrincipalWrongTypeError"
@IsString()
message = "The princial must have an existing principal's id. \n You provided a object of another type."
message = "The principal must have an existing principal's id. \n You provided a object of another type."
}
+48
View File
@@ -0,0 +1,48 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a card couldn't be found.
*/
export class RunnerCardNotFoundError extends NotFoundError {
@IsString()
name = "RunnerCardNotFoundError"
@IsString()
message = "Card not found!"
}
/**
* Error to throw when two cards' ids don't match.
* Usually occurs when a user tries to change a card's id.
*/
export class RunnerCardIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerCardIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a cards's id: This isn't allowed"
}
/**
* Error to throw when a card still has scans associated.
*/
export class RunnerCardHasScansError extends NotAcceptableError {
@IsString()
name = "RunnerCardHasScansError"
@IsString()
message = "This card still has scans associated with it. \n If you want to delete this card with all it's scans add `?force` to your query. \n Otherwise please consider just disabling it."
}
/**
* Error to throw when a card's id is too big to generate a ean-13 barcode for it.
* This error should never reach a end user.
*/
export class RunnerCardIdOutOfRangeError extends Error {
@IsString()
name = "RunnerCardIdOutOfRangeError"
@IsString()
message = "The card's id is too big to fit into a ean-13 barcode. \n This has a very low probability of happening but means that you might want to switch your barcode format for something that can accept numbers over 9999999999."
}
+34 -1
View File
@@ -32,5 +32,38 @@ export class RunnerGroupNeededError extends NotAcceptableError {
name = "RunnerGroupNeededError"
@IsString()
message = "Runner's need to be part of one group (team or organisiation)! \n You provided neither."
message = "Runner's need to be part of one group (team or organization)! \n You provided neither."
}
/**
* Error to throw when a citizen runner has no mail-address.
*/
export class RunnerEmailNeededError extends NotAcceptableError {
@IsString()
name = "RunnerEmailNeededError"
@IsString()
message = "Citizenrunners have to provide an email address for verification and contacting."
}
/**
* Error to throw when a runner already requested a new selfservice link in the last 24hrs.
*/
export class RunnerSelfserviceTimeoutError extends NotAcceptableError {
@IsString()
name = "RunnerSelfserviceTimeoutError"
@IsString()
message = "You can only reqest a new token every 24hrs."
}
/**
* Error to throw when a runner still has distance donations associated.
*/
export class RunnerHasDistanceDonationsError extends NotAcceptableError {
@IsString()
name = "RunnerHasDistanceDonationsError"
@IsString()
message = "This runner still has distance donations associated with it. \n If you want to delete this runner with all it's donations and teams add `?force` to your query."
}
-58
View File
@@ -1,58 +0,0 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner organisation couldn't be found.
*/
export class RunnerOrganisationNotFoundError extends NotFoundError {
@IsString()
name = "RunnerOrganisationNotFoundError"
@IsString()
message = "RunnerOrganisation not found!"
}
/**
* Error to throw when two runner organisations' ids don't match.
* Usually occurs when a user tries to change a runner organisation's id.
*/
export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerOrganisationIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a runner organisation's id: This isn't allowed!"
}
/**
* Error to throw when a organisation still has runners associated.
*/
export class RunnerOrganisationHasRunnersError extends NotAcceptableError {
@IsString()
name = "RunnerOrganisationHasRunnersError"
@IsString()
message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
}
/**
* Error to throw when a organisation still has teams associated.
*/
export class RunnerOrganisationHasTeamsError extends NotAcceptableError {
@IsString()
name = "RunnerOrganisationHasTeamsError"
@IsString()
message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
}
/**
* Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
*/
export class RunnerOrganisationWrongTypeError extends NotAcceptableError {
@IsString()
name = "RunnerOrganisationWrongTypeError"
@IsString()
message = "The runner organisation must be an existing organisation's id. \n You provided a object of another type."
}
+58
View File
@@ -0,0 +1,58 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner organization couldn't be found.
*/
export class RunnerOrganizationNotFoundError extends NotFoundError {
@IsString()
name = "RunnerOrganizationNotFoundError"
@IsString()
message = "RunnerOrganization not found!"
}
/**
* Error to throw when two runner organization's ids don't match.
* Usually occurs when a user tries to change a runner organization's id.
*/
export class RunnerOrganizationIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerOrganizationIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a runner organization's id: This isn't allowed!"
}
/**
* Error to throw when a organization still has runners associated.
*/
export class RunnerOrganizationHasRunnersError extends NotAcceptableError {
@IsString()
name = "RunnerOrganizationHasRunnersError"
@IsString()
message = "This organization still has runners associated with it. \n If you want to delete this organization with all it's runners and teams add `?force` to your query."
}
/**
* Error to throw when a organization still has teams associated.
*/
export class RunnerOrganizationHasTeamsError extends NotAcceptableError {
@IsString()
name = "RunnerOrganizationHasTeamsError"
@IsString()
message = "This organization still has teams associated with it. \n If you want to delete this organization with all it's runners and teams add `?force` to your query."
}
/**
* Error to throw, when a provided runnerOrganization doesn't belong to the accepted types.
*/
export class RunnerOrganizationWrongTypeError extends NotAcceptableError {
@IsString()
name = "RunnerOrganizationWrongTypeError"
@IsString()
message = "The runner organization must be an existing organization's id. \n You provided a object of another type."
}
+1 -1
View File
@@ -43,5 +43,5 @@ export class RunnerTeamNeedsParentError extends NotAcceptableError {
name = "RunnerTeamNeedsParentError"
@IsString()
message = "You provided no runner organisation as this team's parent group."
message = "You provided no runner organization as this team's parent group."
}
+25
View File
@@ -0,0 +1,25 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a Scan couldn't be found.
*/
export class ScanNotFoundError extends NotFoundError {
@IsString()
name = "ScanNotFoundError"
@IsString()
message = "Scan not found!"
}
/**
* Error to throw when two Scans' ids don't match.
* Usually occurs when a user tries to change a Scan's id.
*/
export class ScanIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "ScanIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a Scan's id: This isn't allowed!"
}
+36
View File
@@ -0,0 +1,36 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw, when a non-existent scan station get's loaded.
*/
export class ScanStationNotFoundError extends NotFoundError {
@IsString()
name = "ScanStationNotFoundError"
@IsString()
message = "The scan station you provided couldn't be located in the system. \n Please check your request."
}
/**
* Error to throw when two scan stations' ids don't match.
* Usually occurs when a user tries to change a scan station's id.
*/
export class ScanStationIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "ScanStationIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a scan station's id: This isn't allowed!"
}
/**
* Error to throw when a station still has scans associated.
*/
export class ScanStationHasScansError extends NotAcceptableError {
@IsString()
name = "ScanStationHasScansError"
@IsString()
message = "This station still has scans associated with it. \n If you want to delete this station with all it's scans add `?force` to your query."
}
+1 -1
View File
@@ -2,7 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw, when a non-existant stats client get's loaded.
* Error to throw, when a non-existent stats client get's loaded.
*/
export class StatsClientNotFoundError extends NotFoundError {
@IsString()
+8
View File
@@ -33,4 +33,12 @@ export class TrackLapTimeCantBeNegativeError extends NotAcceptableError {
@IsString()
message = "The minimum lap time you provided is negative - That isn't possible. \n If you wanted to disable it: Just set it to 0/null."
}
export class TrackHasScanStationsError extends NotAcceptableError {
@IsString()
name = "TrackHasScanStationsError"
@IsString()
message = "This track still has stations associated with it. \n If you want to delete this track with all it's stations and scans add `?force` to your query."
}
+37 -1
View File
@@ -4,7 +4,7 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when no username or email is set.
* We somehow need to identify you :)
* We somehow need to identify you on login.
*/
export class UsernameOrEmailNeededError extends NotFoundError {
@IsString()
@@ -14,6 +14,30 @@ export class UsernameOrEmailNeededError extends NotFoundError {
message = "No username or email is set!"
}
/**
* Error to throw when no username contains illegal characters.
* Right now the only one is "@" but this could change in the future.
*/
export class UsernameContainsIllegalCharacterError extends NotAcceptableError {
@IsString()
name = "UsernameContainsIllegalCharacterError"
@IsString()
message = "The provided username contains illegal characters! \n Right now the following characters are considered illegal: '@'"
}
/**
* Error to throw when no email is set.
* We somehow need to identify you :)
*/
export class UserEmailNeededError extends NotFoundError {
@IsString()
name = "UserEmailNeededError"
@IsString()
message = "No email is set! \n You have to provide email addresses for users (used for password reset among others)."
}
/**
* Error to throw when a user couldn't be found.
*/
@@ -35,4 +59,16 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
@IsString()
message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
}
/**
* Error to throw when two users' ids don't match.
* Usually occurs when a user tries to change a user's id.
*/
export class UserDeletionNotConfirmedError extends NotAcceptableError {
@IsString()
name = "UserDeletionNotConfirmedError"
@IsString()
message = "You are trying to delete a user! \n If you're sure about doing this: provide the ?force=true query param."
}
+5 -5
View File
@@ -2,7 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when no groupname is set.
* Error to throw when no group name is set.
*/
export class GroupNameNeededError extends NotFoundError {
@IsString()
@@ -13,7 +13,7 @@ export class GroupNameNeededError extends NotFoundError {
}
/**
* Error to throw when a usergroup couldn't be found.
* Error to throw when a user group couldn't be found.
*/
export class UserGroupNotFoundError extends NotFoundError {
@IsString()
@@ -24,13 +24,13 @@ export class UserGroupNotFoundError extends NotFoundError {
}
/**
* Error to throw when two usergroups' ids don't match.
* Usually occurs when a user tries to change a usergroups's id.
* Error to throw when two user groups' ids don't match.
* Usually occurs when a user tries to change a user groups's id.
*/
export class UserGroupIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "UserGroupIdsNotMatchingError"
@IsString()
message = "The ids don't match!! \n If you wanted to change a usergroup's id: This isn't allowed!"
message = "The ids don't match!! \n If you wanted to change a user group's id: This isn't allowed!"
}
+15 -18
View File
@@ -1,6 +1,7 @@
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
import * as jsonwebtoken from "jsonwebtoken";
import { config } from './config';
import { Runner } from './models/entities/Runner';
import { User } from './models/entities/User';
/**
@@ -34,6 +35,19 @@ export class JwtCreator {
}, config.jwt_secret)
}
/**
* Creates a new selfservice token for a given runner.
* @param runner Runner entity that the access token shall be created for.
* @param expiry_timestamp Timestamp for the token expiry. Will be set about 9999 years if none provided.
*/
public static createSelfService(runner: Runner, expiry_timestamp?: number) {
if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 36000 * 60 * 24 * 365 * 9999; }
return jsonwebtoken.sign({
id: runner.id,
exp: expiry_timestamp
}, config.jwt_secret)
}
/**
* Creates a new password reset token for a given user.
* The token is valid for 15 minutes or 1 use - whatever comes first.
@@ -106,23 +120,6 @@ export class JwtUser {
this.refreshTokenCount = user.refreshTokenCount;
this.uuid = user.uuid;
this.profilePic = user.profilePic;
this.permissions = this.getPermissions(user);
}
/**
* Handels getting the permissions granted to this user (direct or indirect).
* @param user User which's permissions shall be gotten.
*/
public getPermissions(user: User): string[] {
let returnPermissions: string[] = new Array<string>();
for (let permission of user.permissions) {
returnPermissions.push(permission.toString());
}
for (let group of user.groups) {
for (let permission of group.permissions) {
returnPermissions.push(permission.toString());
}
}
return Array.from(new Set(returnPermissions));
this.permissions = user.allPermissions;
}
}
+17 -2
View File
@@ -1,6 +1,9 @@
import { createConnection } from "typeorm";
import { runSeeder } from 'typeorm-seeding';
import { User } from '../models/entities/User';
import { config } from '../config';
import { ConfigFlag } from '../models/entities/ConfigFlags';
import SeedPublicOrg from '../seeds/SeedPublicOrg';
import SeedTestRunners from '../seeds/SeedTestRunners';
import SeedUsers from '../seeds/SeedUsers';
/**
* Loader for the database that creates the database connection and initializes the database tabels.
@@ -9,8 +12,20 @@ import SeedUsers from '../seeds/SeedUsers';
export default async () => {
const connection = await createConnection();
await connection.synchronize();
if (await connection.getRepository(User).count() === 0) {
//The data seeding part
if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:user", value: "true" }))) {
await runSeeder(SeedUsers);
await connection.getRepository(ConfigFlag).save({ option: "seeded:user", value: "true" });
}
if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:citizenorg", value: "true" }))) {
await runSeeder(SeedPublicOrg);
await connection.getRepository(ConfigFlag).save({ option: "seeded:citizenorg", value: "true" });
}
if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:testdata", value: "true" })) && config.seedTestData == true) {
await runSeeder(SeedTestRunners);
await connection.getRepository(ConfigFlag).save({ option: "seeded:testdata", value: "true" });
}
return connection;
};
+3 -37
View File
@@ -1,8 +1,8 @@
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
import { validationMetadatasToSchemas } from "@odit/class-validator-jsonschema";
import express, { Application } from "express";
import path from 'path';
import { getMetadataArgsStorage } from "routing-controllers";
import { routingControllersToSpec } from "routing-controllers-openapi";
import { generateSpec } from '../apispec';
/**
* Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
@@ -15,41 +15,7 @@ export default async (app: Application) => {
});
//Spec creation based on the previously created schemas
const spec = routingControllersToSpec(
storage,
{
routePrefix: "/api"
},
{
components: {
schemas,
"securitySchemes": {
"AuthToken": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
},
"RefreshTokenCookie": {
"type": "apiKey",
"in": "cookie",
"name": "lfk_backend__refresh_token",
description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
},
"StatsApiToken": {
"type": "http",
"scheme": "bearer",
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients)."
}
}
},
info: {
description: "The the backend API for the LfK! runner system.",
title: "LfK! Backend API",
version: "0.0.5",
},
}
);
const spec = generateSpec(storage, schemas);
app.get(["/api/docs/openapi.json", "/api/docs/swagger.json"], (req, res) => {
res.json(spec);
});
+64
View File
@@ -0,0 +1,64 @@
import axios from 'axios';
import { config } from './config';
import { MailSendingError } from './errors/MailErrors';
/**
* This class is responsible for all things mail sending.
* This uses axios to communicate with the mailer api (https://git.odit.services/lfk/mailer).
*/
export class Mailer {
public static base: string = config.mailer_url;
public static key: string = config.mailer_key;
public static testing: boolean = config.testing;
/**
* Function for sending a password reset mail.
* @param to_address The address the mail will be sent to. Should always get pulled from a user object.
* @param token The requested password reset token - will be combined with the app_url to generate a password reset link.
*/
public static async sendResetMail(to_address: string, token: string, locale: string = "en") {
try {
await axios.post(`${Mailer.base}/reset?locale=${locale}&key=${Mailer.key}`, {
address: to_address,
resetKey: token
});
} catch (error) {
if (Mailer.testing) { return true; }
throw new MailSendingError();
}
}
/**
* Function for sending a runner selfservice welcome mail.
* @param to_address The address the mail will be sent to. Should always get pulled from a runner object.
* @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link.
*/
public static async sendSelfserviceWelcomeMail(to_address: string, token: string, locale: string = "en") {
try {
await axios.post(`${Mailer.base}/registration?locale=${locale}&key=${Mailer.key}`, {
address: to_address,
selfserviceToken: token
});
} catch (error) {
if (Mailer.testing) { return true; }
throw new MailSendingError();
}
}
/**
* Function for sending a runner selfservice link forgotten mail.
* @param to_address The address the mail will be sent to. Should always get pulled from a runner object.
* @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link.
*/
public static async sendSelfserviceForgottenMail(to_address: string, token: string, locale: string = "en") {
try {
await axios.post(`${Mailer.base}/registration_forgot?locale=${locale}&key=${Mailer.key}`, {
address: to_address,
selfserviceToken: token
});
} catch (error) {
if (Mailer.testing) { return true; }
throw new MailSendingError();
}
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { Request, Response } from 'express';
/**
* Custom express middleware that appends the raw body to the request obeject.
* Mainly used for parsing csvs from boddies.
* Custom express middleware that appends the raw body to the request object.
* Mainly used for parsing csvs from bodies.
*/
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
+69
View File
@@ -0,0 +1,69 @@
import * as argon2 from "argon2";
import { Request, Response } from 'express';
import { getConnectionManager } from 'typeorm';
import { ScanStation } from '../models/entities/ScanStation';
import authchecker from './authchecker';
/**
* This middleware handles the authentication of scan station api tokens.
* The tokens have to be provided via Bearer authorization header.
* You have to manually use this middleware via @UseBefore(ScanAuth) instead of using @Authorized().
* @param req Express request object.
* @param res Express response object.
* @param next Next function to call on success.
*/
const ScanAuth = async (req: Request, res: Response, next: () => void) => {
let provided_token: string = req.headers["authorization"];
if (provided_token == "" || provided_token === undefined || provided_token === null) {
res.status(401).send({ http_code: 401, short: "no_token", message: "No api token provided." });
return;
}
try {
provided_token = provided_token.replace("Bearer ", "");
} catch (error) {
res.status(401).send({ http_code: 401, short: "no_token", message: "No valid jwt or api token provided." });
return;
}
let prefix = "";
try {
prefix = provided_token.split(".")[0];
}
finally {
if (prefix == "" || prefix == undefined || prefix == null) {
res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." });
return;
}
}
const station = await getConnectionManager().get().getRepository(ScanStation).findOne({ prefix: prefix });
if (!station) {
let user_authorized = false;
try {
let action = { request: req, response: res, context: null, next: next }
user_authorized = await authchecker(action, ["SCAN:CREATE"]);
}
finally {
if (user_authorized == false) {
res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." });
return;
}
else {
next();
}
}
}
else {
if (station.enabled == false) {
res.status(401).send({ http_code: 401, short: "station_disabled", message: "Station is disabled." });
}
if (!(await argon2.verify(station.key, provided_token))) {
res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." });
return;
}
req.headers["station_id"] = station.id.toString();
next();
}
}
export default ScanAuth;
+4 -3
View File
@@ -5,8 +5,9 @@ import { StatsClient } from '../models/entities/StatsClient';
import authchecker from './authchecker';
/**
* This middleware handels the authentification of stats client api tokens.
* The tokens have to be provided via Bearer auth header.
* This middleware handles the authentication of stats client api tokens.
* The tokens have to be provided via Bearer authorization header.
* You have to manually use this middleware via @UseBefore(StatsAuth) instead of using @Authorized().
* @param req Express request object.
* @param res Express response object.
* @param next Next function to call on success.
@@ -41,7 +42,7 @@ const StatsAuth = async (req: Request, res: Response, next: () => void) => {
let user_authorized = false;
try {
let action = { request: req, response: res, context: null, next: next }
user_authorized = await authchecker(action, ["RUNNER:GET", "TEAM:GET", "ORGANISATION:GET"]);
user_authorized = await authchecker(action, ["RUNNER:GET", "TEAM:GET", "ORGANIZATION:GET"]);
}
finally {
if (user_authorized == false) {
+58
View File
@@ -0,0 +1,58 @@
import cookie from "cookie";
import * as jwt from "jsonwebtoken";
import { Action } from 'routing-controllers';
import { getConnectionManager } from 'typeorm';
import { config } from '../config';
import { IllegalJWTError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from '../errors/AuthError';
import { JwtCreator, JwtUser } from '../jwtcreator';
import { User } from '../models/entities/User';
/**
* TODO:
*/
const UserChecker = async (action: Action) => {
let jwtPayload = undefined
try {
let provided_token = "" + action.request.headers["authorization"].replace("Bearer ", "");
jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret);
jwtPayload = jwtPayload["userdetails"];
} catch (error) {
jwtPayload = await refresh(action);
}
const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] })
if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
if (user.enabled == false) { throw new UserDisabledError(); }
return user;
};
/**
* Handles soft-refreshing of access-tokens.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
*/
const refresh = async (action: Action) => {
let refresh_token = undefined;
try {
refresh_token = cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"];
}
catch {
throw new IllegalJWTError();
}
let jwtPayload = undefined;
try {
jwtPayload = <any>jwt.verify(refresh_token, config.jwt_secret);
} catch (error) {
throw new IllegalJWTError();
}
const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions', 'groups', 'groups.permissions'] })
if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
if (user.enabled == false) { throw new UserDisabledError(); }
let newAccess = JwtCreator.createAccess(user);
action.response.header("authorization", "Bearer " + newAccess);
return await new JwtUser(user);
}
export default UserChecker;
+2 -2
View File
@@ -8,7 +8,7 @@ import { JwtCreator, JwtUser } from '../jwtcreator';
import { User } from '../models/entities/User';
/**
* Handels authorisation verification via jwt's for all api endpoints using the @Authorized decorator.
* Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
* @param permissions The permissions that the endpoint using @Authorized requires.
*/
@@ -43,7 +43,7 @@ const authchecker = async (action: Action, permissions: string[] | string) => {
}
/**
* Handels soft-refreshing of access-tokens.
* Handles soft-refreshing of access-tokens.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
*/
const refresh = async (action: Action) => {
-70
View File
@@ -1,70 +0,0 @@
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
import { config } from '../../config';
import { Address } from '../entities/Address';
/**
* This classed is used to create a new Address entity from a json body (post request).
*/
export class CreateAddress {
/**
* The newaddress's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* The new address's first line.
* Containing the street and house number.
*/
@IsString()
@IsNotEmpty()
address1: string;
/**
* The new address's second line.
* Containing optional information.
*/
@IsString()
@IsOptional()
address2?: string;
/**
* The new address's postal code.
* This will get checked against the postal code syntax for the configured country.
* TODO: Implement the config option.
*/
@IsString()
@IsNotEmpty()
@IsPostalCode(config.postalcode_validation_countrycode)
postalcode: string;
/**
* The new address's city.
*/
@IsString()
@IsNotEmpty()
city: string;
/**
* The new address's country.
*/
@IsString()
@IsNotEmpty()
country: string;
/**
* Creates a new Address entity from this.
*/
public toAddress(): Address {
let newAddress: Address = new Address();
newAddress.address1 = this.address1;
newAddress.address2 = this.address2;
newAddress.postalcode = this.postalcode;
newAddress.city = this.city;
newAddress.country = this.country;
return newAddress;
}
}
-85
View File
@@ -1,85 +0,0 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
import { GroupContact } from '../entities/GroupContact';
/**
* This classed is used to create a new Group entity from a json body (post request).
*/
export class CreateGroupContact {
/**
* The new contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The new contact's middle name.
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The new contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The new contact's address.
* Must be the address's id.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* The contact's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* Gets the new contact's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined || this.address === null) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
/**
* Creates a new Address entity from this.
*/
public async toGroupContact(): Promise<GroupContact> {
let contact: GroupContact = new GroupContact();
contact.firstname = this.firstname;
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.email = this.email;
contact.phone = this.phone;
contact.address = await this.getAddress();
return null;
}
}
-72
View File
@@ -1,72 +0,0 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
/**
* This classed is used to create a new Participant entity from a json body (post request).
*/
export abstract class CreateParticipant {
/**
* The new participant's first name.
*/
@IsString()
@IsNotEmpty()
firstname: string;
/**
* The new participant's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new participant's last name.
*/
@IsString()
@IsNotEmpty()
lastname: string;
/**
* The new participant's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsString()
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The new participant's e-mail address.
*/
@IsString()
@IsOptional()
@IsEmail()
email?: string;
/**
* The new participant's address.
* Must be of type number (address id).
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Gets the new participant's address by it's address.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined || this.address === null) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
}
-40
View File
@@ -1,40 +0,0 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors';
import { GroupContact } from '../entities/GroupContact';
/**
* This classed is used to create a new RunnerGroup entity from a json body (post request).
*/
export abstract class CreateRunnerGroup {
/**
* The new group's name.
*/
@IsNotEmpty()
@IsString()
name: string;
/**
* The new group's contact.
* Optional
*/
@IsInt()
@IsOptional()
contact?: number;
/**
* Gets the new group's contact by it's id.
*/
public async getContact(): Promise<GroupContact> {
if (this.contact === undefined || this.contact === null) {
return null;
}
if (!isNaN(this.contact)) {
let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
if (!contact) { throw new GroupContactNotFoundError; }
return contact;
}
throw new GroupContactWrongTypeError;
}
}
@@ -1,48 +0,0 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This classed is used to create a new RunnerOrganisation entity from a json body (post request).
*/
export class CreateRunnerOrganisation extends CreateRunnerGroup {
/**
* The new organisation's address.
* Must be of type number (address id).
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Gets the org's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined || this.address === null) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
/**
* Creates a new RunnerOrganisation entity from this.
*/
public async toRunnerOrganisation(): Promise<RunnerOrganisation> {
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
newRunnerOrganisation.name = this.name;
newRunnerOrganisation.contact = await this.getContact();
// newRunnerOrganisation.address = await this.getAddress();
return newRunnerOrganisation;
}
}
+5 -5
View File
@@ -1,11 +1,11 @@
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNeededError } from '../../errors/RunnerErrors';
import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisationErrors';
import { RunnerOrganizationNotFoundError } from '../../errors/RunnerOrganizationErrors';
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerOrganization } from '../entities/RunnerOrganization';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunner } from './CreateRunner';
import { CreateRunner } from './create/CreateRunner';
/**
* Special class used to import runners from csv files - or json arrays created from csv to be exact.
@@ -78,9 +78,9 @@ export class ImportRunner {
let team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: groupID });
if (team) { return team; }
let org = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: groupID });
let org = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: groupID });
if (!org) {
throw new RunnerOrganisationNotFoundError();
throw new RunnerOrganizationNotFoundError();
}
if (this.team === undefined) { return org; }
+3 -3
View File
@@ -5,7 +5,7 @@ import { config } from '../../config';
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
import { JwtCreator } from "../../jwtcreator";
import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth';
import { ResponseAuth } from '../responses/ResponseAuth';
/**
* This class is used to create refreshed auth credentials.
@@ -24,8 +24,8 @@ export class RefreshAuth {
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();
public async toAuth(): Promise<ResponseAuth> {
let newAuth: ResponseAuth = new ResponseAuth();
if (!this.token || this.token === undefined) {
throw new JwtNotProvidedError()
}
-59
View File
@@ -1,59 +0,0 @@
import { IsInt, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
/**
* This class is used to update a Runner entity (via put request).
*/
export class UpdateRunner extends CreateParticipant {
/**
* The updated runner's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated runner's new team/org.
* Just has to contain the group's id -everything else won't be checked or changed.
*/
@IsObject()
group: RunnerGroup;
/**
* Updates a provided Runner entity based on this.
*/
public async updateRunner(runner: Runner): Promise<Runner> {
runner.firstname = this.firstname;
runner.middlename = this.middlename;
runner.lastname = this.lastname;
runner.phone = this.phone;
runner.email = this.email;
runner.group = await this.getGroup();
runner.address = await this.getAddress();
return runner;
}
/**
* Loads the updated runner's group based on it's id.
*/
public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.group.id)) {
let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group.id });
if (!group) { throw new RunnerGroupNotFoundError; }
return group;
}
throw new RunnerOrganisationWrongTypeError;
}
}
@@ -1,52 +0,0 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This class is used to update a RunnerOrganisation entity (via put request).
*/
export class UpdateRunnerOrganisation extends CreateRunnerGroup {
/**
* The updated orgs's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated organisation's address.
* Just has to contain the address's id - everything else won't be checked or changed.
* Optional.
*/
@IsInt()
@IsOptional()
address?: Address;
/**
* Loads the organisation's address based on it's id.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined || this.address === null) {
return null;
}
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address.id });
if (!address) { throw new AddressNotFoundError; }
return address;
}
/**
* Updates a provided RunnerOrganisation entity based on this.
*/
public async updateRunnerOrganisation(organisation: RunnerOrganisation): Promise<RunnerOrganisation> {
organisation.name = this.name;
organisation.contact = await this.getContact();
// organisation.address = await this.getAddress();
return organisation;
}
}
-56
View File
@@ -1,56 +0,0 @@
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This class is used to update a RunnerTeam entity (via put request).
*/
export class UpdateRunnerTeam extends CreateRunnerGroup {
/**
* The updated team's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated team's parentGroup.
* Just has to contain the organisation's id - everything else won't be checked or changed.
*/
@IsObject()
@IsNotEmpty()
parentGroup: RunnerOrganisation;
/**
* Loads the updated teams's parentGroup based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.parentGroup.id)) {
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup.id });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
return parentGroup;
}
throw new RunnerOrganisationWrongTypeError;
}
/**
* Updates a provided RunnerTeam entity based on this.
*/
public async updateRunnerTeam(team: RunnerTeam): Promise<RunnerTeam> {
team.name = this.name;
team.parentGroup = await this.getParent();
team.contact = await this.getContact()
return team;
}
}
@@ -1,11 +1,11 @@
import * as argon2 from "argon2";
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
import { JwtCreator } from '../../jwtcreator';
import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth';
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
import { JwtCreator } from '../../../jwtcreator';
import { User } from '../../entities/User';
import { ResponseAuth } from '../../responses/ResponseAuth';
/**
* This class is used to create auth credentials based on user credentials provided in a json body (post request).
@@ -42,8 +42,8 @@ export class CreateAuth {
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();
public async toAuth(): Promise<ResponseAuth> {
let newAuth: ResponseAuth = new ResponseAuth();
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
@@ -0,0 +1,52 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { DistanceDonation } from '../../entities/DistanceDonation';
import { Runner } from '../../entities/Runner';
import { CreateDonation } from './CreateDonation';
/**
* This class is used to create a new FixedDonation entity from a json body (post request).
*/
export class CreateDistanceDonation extends CreateDonation {
/**
* The donation's associated runner's id.
* This is important to link the runner's distance ran to the donation.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* The donation's amount per distance (full kilometer aka 1000 meters).
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amountPerDistance: number;
/**
* Creates a new FixedDonation entity from this.
*/
public async toEntity(): Promise<DistanceDonation> {
let newDonation = new DistanceDonation;
newDonation.amountPerDistance = this.amountPerDistance;
newDonation.donor = await this.getDonor();
newDonation.runner = await this.getRunner();
return newDonation;
}
/**
* Gets a runner based on the runner id provided via this.runner.
*/
public async getRunner(): Promise<Runner> {
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}
@@ -0,0 +1,34 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation';
import { Donor } from '../../entities/Donor';
/**
* This class is used to create a new Donation entity from a json body (post request).
*/
export abstract class CreateDonation {
/**
* The donation's associated donor's id.
* This is important to link donations to donors.
*/
@IsInt()
@IsPositive()
donor: number;
/**
* Creates a new Donation entity from this.
*/
public abstract toEntity(): Promise<Donation>;
/**
* Gets a donor based on the donor id provided via this.donor.
*/
public async getDonor(): Promise<Donor> {
const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
if (!donor) {
throw new DonorNotFoundError();
}
return donor;
}
}
@@ -1,6 +1,7 @@
import { IsBoolean, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
import { Donor } from '../entities/Donor';
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
import { Address } from '../../entities/Address';
import { Donor } from '../../entities/Donor';
import { CreateParticipant } from './CreateParticipant';
/**
@@ -18,7 +19,7 @@ export class CreateDonor extends CreateParticipant {
/**
* Creates a new Donor entity from this.
*/
public async toDonor(): Promise<Donor> {
public async toEntity(): Promise<Donor> {
let newDonor: Donor = new Donor();
newDonor.firstname = this.firstname;
@@ -26,10 +27,10 @@ export class CreateDonor extends CreateParticipant {
newDonor.lastname = this.lastname;
newDonor.phone = this.phone;
newDonor.email = this.email;
newDonor.address = await this.getAddress();
newDonor.receiptNeeded = this.receiptNeeded;
if (this.receiptNeeded == true && this.address == null) {
newDonor.address = this.address;
Address.validate(newDonor.address);
if (this.receiptNeeded == true && Address.isValidAddress(newDonor.address) == false) {
throw new DonorReceiptAddressNeededError()
}
@@ -0,0 +1,28 @@
import { IsInt, IsPositive } from 'class-validator';
import { FixedDonation } from '../../entities/FixedDonation';
import { CreateDonation } from './CreateDonation';
/**
* This class is used to create a new FixedDonation entity from a json body (post request).
*/
export class CreateFixedDonation extends CreateDonation {
/**
* The donation's amount.
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amount: number;
/**
* Creates a new FixedDonation entity from this.
*/
public async toEntity(): Promise<FixedDonation> {
let newDonation = new FixedDonation;
newDonation.amount = this.amount;
newDonation.donor = await this.getDonor();
return newDonation;
}
}
@@ -0,0 +1,97 @@
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact';
import { RunnerGroup } from '../../entities/RunnerGroup';
/**
* This classed is used to create a new GroupContact entity from a json body (post request).
*/
export class CreateGroupContact {
/**
* The new contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The new contact's middle name.
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The new contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The new contact's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* The contact's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The new contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* The new contacts's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* Get's all groups for this contact by their id's;
*/
public async getGroups(): Promise<RunnerGroup[]> {
if (!this.groups) { return null; }
let groups = new Array<RunnerGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
if (!found) { throw new RunnerGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
/**
* Creates a new GroupContact entity from this.
*/
public async toEntity(): Promise<GroupContact> {
let newContact: GroupContact = new GroupContact();
newContact.firstname = this.firstname;
newContact.middlename = this.middlename;
newContact.lastname = this.lastname;
newContact.email = this.email;
newContact.phone = this.phone;
newContact.address = this.address;
Address.validate(newContact.address);
newContact.groups = await this.getGroups();
return newContact;
}
}
@@ -0,0 +1,53 @@
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { config } from '../../../config';
import { Address } from '../../entities/Address';
/**
* This classed is used to create a new Participant entity from a json body (post request).
*/
export abstract class CreateParticipant {
/**
* The new participant's first name.
*/
@IsString()
@IsNotEmpty()
firstname: string;
/**
* The new participant's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new participant's last name.
*/
@IsString()
@IsNotEmpty()
lastname: string;
/**
* The new participant's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsString()
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The new participant's e-mail address.
*/
@IsString()
@IsOptional()
@IsEmail()
email?: string;
/**
* The new participant's address.
*/
@IsOptional()
@IsObject()
address?: Address;
}
@@ -4,11 +4,11 @@ import {
IsNotEmpty
} from "class-validator";
import { getConnectionManager } from 'typeorm';
import { PrincipalNotFoundError } from '../../errors/PrincipalErrors';
import { Permission } from '../entities/Permission';
import { Principal } from '../entities/Principal';
import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
import { Permission } from '../../entities/Permission';
import { Principal } from '../../entities/Principal';
import { PermissionAction } from '../../enums/PermissionAction';
import { PermissionTarget } from '../../enums/PermissionTargets';
/**
* This classed is used to create a new Permission entity from a json body (post request).
@@ -39,7 +39,7 @@ export class CreatePermission {
/**
* Creates a new Permission entity from this.
*/
public async toPermission(): Promise<Permission> {
public async toEntity(): Promise<Permission> {
let newPermission: Permission = new Permission();
newPermission.principal = await this.getPrincipal();
@@ -1,39 +1,33 @@
import { IsEmail, IsOptional, IsString } from 'class-validator';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
import { JwtCreator } from '../../jwtcreator';
import { User } from '../entities/User';
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
import { UserEmailNeededError } from '../../../errors/UserErrors';
import { JwtCreator } from '../../../jwtcreator';
import { User } from '../../entities/User';
/**
* This calss is used to create password reset tokens for users.
* This class is used to create password reset tokens for users.
* These password reset token can be used to set a new password for the user for the next 15mins.
*/
export class CreateResetToken {
/**
* The username of the user that wants to reset their password.
*/
@IsOptional()
@IsString()
username?: string;
/**
* The email address of the user that wants to reset their password.
*/
@IsOptional()
@IsNotEmpty()
@IsEmail()
@IsString()
email?: string;
email: string;
/**
* Create a password reset token based on this.
*/
public async toResetToken(): Promise<any> {
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
public async toResetToken(): Promise<string> {
if (!this.email) {
throw new UserEmailNeededError();
}
let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { email: this.email }] });
let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ email: this.email }] });
if (!found_user) { throw new UserNotFoundError(); }
if (found_user.enabled == false) { throw new UserDisabledError(); }
if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); }
@@ -43,7 +37,7 @@ export class CreateResetToken {
await getConnectionManager().get().getRepository(User).save(found_user);
//Create the reset token
let reset_token = JwtCreator.createReset(found_user);
let reset_token: string = JwtCreator.createReset(found_user);
return reset_token;
}
@@ -1,10 +1,11 @@
import { IsInt } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { RunnerOrganizationWrongTypeError } from '../../../errors/RunnerOrganizationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner';
import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
/**
@@ -21,7 +22,7 @@ export class CreateRunner extends CreateParticipant {
/**
* Creates a new Runner entity from this.
*/
public async toRunner(): Promise<Runner> {
public async toEntity(): Promise<Runner> {
let newRunner: Runner = new Runner();
newRunner.firstname = this.firstname;
@@ -30,7 +31,8 @@ export class CreateRunner extends CreateParticipant {
newRunner.phone = this.phone;
newRunner.email = this.email;
newRunner.group = await this.getGroup();
newRunner.address = await this.getAddress();
newRunner.address = this.address;
Address.validate(newRunner.address);
return newRunner;
}
@@ -48,6 +50,6 @@ export class CreateRunner extends CreateParticipant {
return group;
}
throw new RunnerOrganisationWrongTypeError;
throw new RunnerOrganizationWrongTypeError;
}
}
@@ -0,0 +1,45 @@
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { Runner } from '../../entities/Runner';
import { RunnerCard } from '../../entities/RunnerCard';
/**
* This classed is used to create a new RunnerCard entity from a json body (post request).
*/
export class CreateRunnerCard {
/**
* The card's associated runner's id.
*/
@IsInt()
@IsOptional()
runner?: number;
/**
* Is the new card enabled (for fraud reasons)?
* Default: true
*/
@IsBoolean()
enabled: boolean = true;
/**
* Creates a new RunnerCard entity from this.
*/
public async toEntity(): Promise<RunnerCard> {
let newCard: RunnerCard = new RunnerCard();
newCard.enabled = this.enabled;
newCard.runner = await this.getRunner();
return newCard;
}
public async getRunner(): Promise<Runner> {
if (!this.runner) { return null; }
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}
@@ -0,0 +1,35 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { GroupContactNotFoundError } from '../../../errors/GroupContactErrors';
import { GroupContact } from '../../entities/GroupContact';
/**
* This classed is used to create a new RunnerGroup entity from a json body (post request).
*/
export abstract class CreateRunnerGroup {
/**
* The new group's name.
*/
@IsNotEmpty()
@IsString()
name: string;
/**
* The new group's contact's id.
* Optional
*/
@IsInt()
@IsOptional()
contact?: number;
/**
* Gets the new group's contact by it's id.
*/
public async getContact(): Promise<GroupContact> {
if (!this.contact) { return null; }
let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
if (!contact) { throw new GroupContactNotFoundError; }
return contact;
}
}
@@ -0,0 +1,43 @@
import { IsBoolean, IsObject, IsOptional } from 'class-validator';
import * as uuid from 'uuid';
import { Address } from '../../entities/Address';
import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This classed is used to create a new RunnerOrganization entity from a json body (post request).
*/
export class CreateRunnerOrganization extends CreateRunnerGroup {
/**
* The new organization's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* Is registration enabled for the new organization?
*/
@IsOptional()
@IsBoolean()
registrationEnabled?: boolean = false;
/**
* Creates a new RunnerOrganization entity from this.
*/
public async toEntity(): Promise<RunnerOrganization> {
let newRunnerOrganization: RunnerOrganization = new RunnerOrganization();
newRunnerOrganization.name = this.name;
newRunnerOrganization.contact = await this.getContact();
newRunnerOrganization.address = this.address;
Address.validate(newRunnerOrganization.address);
if (this.registrationEnabled) {
newRunnerOrganization.key = uuid.v4().toUpperCase();
}
return newRunnerOrganization;
}
}
@@ -1,9 +1,9 @@
import { IsInt, IsNotEmpty } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { RunnerOrganizationNotFoundError } from '../../../errors/RunnerOrganizationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { RunnerTeam } from '../../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
@@ -12,7 +12,7 @@ import { CreateRunnerGroup } from './CreateRunnerGroup';
export class CreateRunnerTeam extends CreateRunnerGroup {
/**
* The new team's parent group (organisation).
* The new team's parent org's id.
*/
@IsInt()
@IsNotEmpty()
@@ -21,28 +21,23 @@ export class CreateRunnerTeam extends CreateRunnerGroup {
/**
* Gets the new team's parent org based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> {
public async getParent(): Promise<RunnerOrganization> {
if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.parentGroup)) {
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
return parentGroup;
}
throw new RunnerOrganisationWrongTypeError;
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganizationNotFoundError();; }
return parentGroup;
}
/**
* Creates a new RunnerTeam entity from this.
*/
public async toRunnerTeam(): Promise<RunnerTeam> {
public async toEntity(): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam();
newRunnerTeam.name = this.name;
newRunnerTeam.parentGroup = await this.getParent();
newRunnerTeam.contact = await this.getContact()
return newRunnerTeam;
+59
View File
@@ -0,0 +1,59 @@
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { Runner } from '../../entities/Runner';
import { Scan } from '../../entities/Scan';
/**
* This class is used to create a new Scan entity from a json body (post request).
*/
export abstract class CreateScan {
/**
* The scan's associated runner's id.
* This is important to link ran distances to runners.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* Is the scan valid (for fraud reasons).
* The determination of validity will work differently for every child class.
* Default: true
*/
@IsBoolean()
@IsOptional()
valid?: boolean = true;
/**
* The scan's distance in meters.
* Can be set manually or derived from another object.
*/
@IsInt()
@IsPositive()
public distance: number;
/**
* Creates a new Scan entity from this.
*/
public async toEntity(): Promise<Scan> {
let newScan = new Scan();
newScan.distance = this.distance;
newScan.valid = this.valid;
newScan.runner = await this.getRunner();
return newScan;
}
/**
* Gets a runner based on the runner id provided via this.runner.
*/
public async getRunner(): Promise<Runner> {
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}
@@ -0,0 +1,64 @@
import * as argon2 from "argon2";
import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator';
import crypto from 'crypto';
import { getConnection } from 'typeorm';
import * as uuid from 'uuid';
import { TrackNotFoundError } from '../../../errors/TrackErrors';
import { ScanStation } from '../../entities/ScanStation';
import { Track } from '../../entities/Track';
/**
* This class is used to create a new StatsClient entity from a json body (post request).
*/
export class CreateScanStation {
/**
* The new station's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* The station's associated track's id.
*/
@IsInt()
@IsPositive()
track: number;
/**
* Is this station enabled?
*/
@IsBoolean()
@IsOptional()
enabled?: boolean = true;
/**
* Converts this to a ScanStation entity.
*/
public async toEntity(): Promise<ScanStation> {
let newStation: ScanStation = new ScanStation();
newStation.description = this.description;
newStation.enabled = this.enabled;
newStation.track = await this.getTrack();
let newUUID = uuid.v4().toUpperCase();
newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase();
newStation.key = await argon2.hash(newStation.prefix + "." + newUUID);
newStation.cleartextkey = newStation.prefix + "." + newUUID;
return newStation;
}
/**
* Get's a track by it's id provided via this.track.
* Used to link the new station to a track.
*/
public async getTrack(): Promise<Track> {
const track = await getConnection().getRepository(Track).findOne({ id: this.track });
if (!track) {
throw new TrackNotFoundError();
}
return track;
}
}
@@ -0,0 +1,52 @@
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerEmailNeededError } from '../../../errors/RunnerErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner';
import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { CreateParticipant } from './CreateParticipant';
/**
* This classed is used to create a new Runner entity from a json body (post request).
*/
export class CreateSelfServiceCitizenRunner extends CreateParticipant {
/**
* The new runners's e-mail address.
* Must be provided for email-verification to work.
*/
@IsString()
@IsNotEmpty()
@IsEmail()
email: string;
/**
* Creates a new Runner entity from this.
*/
public async toEntity(): Promise<Runner> {
let newRunner: Runner = new Runner();
newRunner.firstname = this.firstname;
newRunner.middlename = this.middlename;
newRunner.lastname = this.lastname;
newRunner.phone = this.phone;
newRunner.email = this.email;
if (!newRunner.email) {
throw new RunnerEmailNeededError();
}
newRunner.group = await this.getGroup();
newRunner.address = this.address;
Address.validate(newRunner.address);
return newRunner;
}
/**
* Gets the new runner's group by it's id.
*/
public async getGroup(): Promise<RunnerOrganization> {
return await getConnection().getRepository(RunnerOrganization).findOne({ id: 1 });
}
}
@@ -0,0 +1,55 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerTeamNotFoundError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner';
import { RunnerGroup } from '../../entities/RunnerGroup';
import { RunnerTeam } from '../../entities/RunnerTeam';
import { CreateParticipant } from './CreateParticipant';
/**
* This classed is used to create a new Runner entity from a json body (post request).
*/
export class CreateSelfServiceRunner extends CreateParticipant {
/**
* The new runner's team's id.
* The team has to be a part of the runner's org.
* The team property may get ignored.
* If no team get's provided the runner's group will be their org.
*/
@IsInt()
@IsOptional()
team?: number;
/**
* Creates a new Runner entity from this.
*/
public async toEntity(group: RunnerGroup): Promise<Runner> {
let newRunner: Runner = new Runner();
newRunner.firstname = this.firstname;
newRunner.middlename = this.middlename;
newRunner.lastname = this.lastname;
newRunner.phone = this.phone;
newRunner.email = this.email;
newRunner.group = await this.getGroup(group);
newRunner.address = this.address;
Address.validate(newRunner.address);
return newRunner;
}
/**
* Gets the new runner's group by it's id.
*/
public async getGroup(group: RunnerGroup): Promise<RunnerGroup> {
if (!this.team) {
return group;
}
const team = await getConnection().getRepository(RunnerTeam).findOne({ id: this.team }, { relations: ["parentGroup"] });
if (!team) { throw new RunnerTeamNotFoundError(); }
if (team.parentGroup.id != group.id) { throw new RunnerTeamNotFoundError(); }
return team;
}
}
@@ -2,7 +2,7 @@ import * as argon2 from "argon2";
import { IsOptional, IsString } from 'class-validator';
import crypto from 'crypto';
import * as uuid from 'uuid';
import { StatsClient } from '../entities/StatsClient';
import { StatsClient } from '../../entities/StatsClient';
/**
* This classed is used to create a new StatsClient entity from a json body (post request).
@@ -18,7 +18,7 @@ export class CreateStatsClient {
/**
* Converts this to a StatsClient entity.
*/
public async toStatsClient(): Promise<StatsClient> {
public async toEntity(): Promise<StatsClient> {
let newClient: StatsClient = new StatsClient();
newClient.description = this.description;
@@ -1,6 +1,6 @@
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
import { Track } from '../entities/Track';
import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors';
import { Track } from '../../entities/Track';
/**
* This classed is used to create a new Track entity from a json body (post request).
@@ -31,7 +31,7 @@ export class CreateTrack {
/**
* Creates a new Track entity from this.
*/
public toTrack(): Track {
public toEntity(): Track {
let newTrack: Track = new Track();
newTrack.name = this.name;
@@ -0,0 +1,100 @@
import { IsInt, IsOptional, IsPositive } from 'class-validator';
import { BadRequestError } from 'routing-controllers';
import { getConnection } from 'typeorm';
import { RunnerCardNotFoundError } from '../../../errors/RunnerCardErrors';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors';
import { RunnerCard } from '../../entities/RunnerCard';
import { ScanStation } from '../../entities/ScanStation';
import { TrackScan } from '../../entities/TrackScan';
/**
* This classed is used to create a new Scan entity from a json body (post request).
*/
export class CreateTrackScan {
/**
* The id of the runnerCard associated with the scan.
* This get's saved for documentation and management purposes.
*/
@IsInt()
@IsPositive()
card: number;
/**
* The scanning station's id that created the scan.
* Mainly used for logging and traceing back scans (or errors).
* You don't have to provide the station if you're authenticateing via a scanstation token (The server takes care of it for you).
*/
@IsInt()
@IsPositive()
@IsOptional()
station?: number;
/**
* Creates a new Track entity from this.
*/
public async toEntity(): Promise<TrackScan> {
let newScan: TrackScan = new TrackScan();
newScan.station = await this.getStation();
newScan.card = await this.getCard();
newScan.track = newScan.station.track;
newScan.runner = newScan.card.runner;
if (!newScan.runner) {
throw new RunnerNotFoundError();
}
newScan.timestamp = Math.round(new Date().getTime() / 1000);
newScan = await this.validateScan(newScan);
return newScan;
}
/**
* Get's a runnerCard entity via the provided id.
* @returns The runnerCard whom's id you provided.
*/
public async getCard(): Promise<RunnerCard> {
const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] });
if (!track) {
throw new RunnerCardNotFoundError();
}
return track;
}
/**
* Get's a scanstation entity via the provided id.
* @returns The scanstation whom's id you provided.
*/
public async getStation(): Promise<ScanStation> {
if (!this.station) {
throw new BadRequestError("You are missing the station's id!")
}
const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ["track"] });
if (!station) {
throw new ScanStationNotFoundError();
}
return station;
}
/**
* Validates the scan and sets it's lap time;
* @param scan The scan you want to validate
* @returns The validated scan with it's laptime set.
*/
public async validateScan(scan: TrackScan): Promise<TrackScan> {
const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] });
if (scans.length == 0) {
scan.lapTime = 0;
scan.valid = true;
}
else {
const newestScan = scans[scans.length - 1];
scan.lapTime = scan.timestamp - newestScan.timestamp;
scan.valid = (scan.lapTime > scan.track.minimumLapTime);
}
return scan;
}
}
@@ -1,124 +1,132 @@
import * as argon2 from "argon2";
import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../config';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup';
/**
* This classed is used to create a new User entity from a json body (post request).
*/
export class CreateUser {
/**
* The new user's first name.
*/
@IsString()
firstname: string;
/**
* The new user's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new user's last name.
*/
@IsString()
lastname: string;
/**
* The new user's username.
* You have to provide at least one of: {email, username}.
*/
@IsOptional()
@IsString()
username?: string;
/**
* The new user's email address.
* You have to provide at least one of: {email, username}.
*/
@IsEmail()
@IsString()
@IsOptional()
email?: string;
/**
* The new user's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsPhoneNumber(config.phone_validation_countrycode)
@IsOptional()
phone?: string;
/**
* The new user's password.
* This will of course not be saved in plaintext :)
*/
@IsString()
password: string;
/**
* Will the new user be enabled from the start?
* Default: true
*/
@IsBoolean()
@IsOptional()
enabled?: boolean = true;
/**
* The new user's groups' id(s).
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
//TODO: ProfilePics
/**
* Converts this to a User entity.
*/
public async toUser(): Promise<User> {
let newUser: User = new User();
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
}
newUser.email = this.email
newUser.username = this.username
newUser.firstname = this.firstname
newUser.middlename = this.middlename
newUser.lastname = this.lastname
newUser.uuid = uuid.v4()
newUser.phone = this.phone
newUser.password = await argon2.hash(this.password + newUser.uuid);
newUser.groups = await this.getGroups();
newUser.enabled = this.enabled;
//TODO: ProfilePics
return newUser;
}
/**
* Get's all groups for this user by their id's;
*/
public async getGroups() {
if (!this.groups) { return null; }
let groups = new Array<UserGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
if (!found) { throw new UserGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
import * as argon2 from "argon2";
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../../config';
import { UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
import { User } from '../../entities/User';
import { UserGroup } from '../../entities/UserGroup';
/**
* This classed is used to create a new User entity from a json body (post request).
*/
export class CreateUser {
/**
* The new user's first name.
*/
@IsString()
firstname: string;
/**
* The new user's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new user's last name.
*/
@IsString()
lastname: string;
/**
* The new user's username.
* You have to provide a email addres, so this is optional.
*/
@IsOptional()
@IsString()
username?: string;
/**
* The new user's email address.
*/
@IsEmail()
@IsString()
@IsNotEmpty()
email: string;
/**
* The new user's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsPhoneNumber(config.phone_validation_countrycode)
@IsOptional()
phone?: string;
/**
* The new user's password.
* This will of course not be saved in plaintext :)
*/
@IsString()
password: string;
/**
* Will the new user be enabled from the start?
* Default: true
*/
@IsBoolean()
@IsOptional()
enabled?: boolean = true;
/**
* The new user's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* The user's profile pic (or rather a url pointing to it).
*/
@IsString()
@IsUrl()
@IsOptional()
profilePic?: string;
/**
* Converts this to a User entity.
*/
public async toEntity(): Promise<User> {
let newUser: User = new User();
if (!this.email) {
throw new UserEmailNeededError();
}
if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
newUser.email = this.email
newUser.username = this.username
newUser.firstname = this.firstname
newUser.middlename = this.middlename
newUser.lastname = this.lastname
newUser.uuid = uuid.v4()
newUser.phone = this.phone
newUser.password = await argon2.hash(this.password + newUser.uuid);
newUser.groups = await this.getGroups();
newUser.enabled = this.enabled;
if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
else { newUser.profilePic = this.profilePic; }
return newUser;
}
/**
* Get's all groups for this user by their id's;
*/
public async getGroups() {
if (!this.groups) { return null; }
let groups = new Array<UserGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
if (!found) { throw new UserGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
}
@@ -1,5 +1,5 @@
import { IsOptional, IsString } from 'class-validator';
import { UserGroup } from '../entities/UserGroup';
import { UserGroup } from '../../entities/UserGroup';
/**
* This classed is used to create a new UserGroup entity from a json body (post request).
@@ -22,7 +22,7 @@ export class CreateUserGroup {
/**
* Creates a new UserGroup entity from this.
*/
public async toUserGroup(): Promise<UserGroup> {
public async toEntity(): Promise<UserGroup> {
let newUserGroup: UserGroup = new UserGroup();
newUserGroup.name = this.name;
@@ -0,0 +1,51 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { DistanceDonation } from '../../entities/DistanceDonation';
import { Runner } from '../../entities/Runner';
import { UpdateDonation } from './UpdateDonation';
/**
* This class is used to update a DistanceDonation entity (via put request).
*/
export class UpdateDistanceDonation extends UpdateDonation {
/**
* The donation's associated runner's id.
* This is important to link the runner's distance ran to the donation.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* The donation's amount per distance (full kilometer aka 1000 meters).
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amountPerDistance: number;
/**
* Update a DistanceDonation entity based on this.
* @param donation The donation that shall be updated.
*/
public async update(donation: DistanceDonation): Promise<DistanceDonation> {
donation.amountPerDistance = this.amountPerDistance;
donation.donor = await this.getDonor();
donation.runner = await this.getRunner();
return donation;
}
/**
* Gets a runner based on the runner id provided via this.runner.
*/
public async getRunner(): Promise<Runner> {
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}
@@ -0,0 +1,41 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation';
import { Donor } from '../../entities/Donor';
/**
* This class is used to update a Donation entity (via put request).
*/
export abstract class UpdateDonation {
/**
* The updated donation's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated donation's associated donor's id.
* This is important to link donations to donors.
*/
@IsInt()
@IsPositive()
donor: number;
/**
* Creates a new Donation entity from this.
*/
public abstract update(donation: Donation): Promise<Donation>;
/**
* Gets a donor based on the donor id provided via this.donor.
*/
public async getDonor(): Promise<Donor> {
const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
if (!donor) {
throw new DonorNotFoundError();
}
return donor;
}
}
@@ -1,7 +1,8 @@
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../errors/DonorErrors';
import { Donor } from '../entities/Donor';
import { CreateParticipant } from './CreateParticipant';
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
import { Address } from '../../entities/Address';
import { Donor } from '../../entities/Donor';
import { CreateParticipant } from '../create/CreateParticipant';
/**
* This class is used to update a Donor entity (via put request).
@@ -26,16 +27,17 @@ export class UpdateDonor extends CreateParticipant {
/**
* Updates a provided Donor entity based on this.
*/
public async updateDonor(donor: Donor): Promise<Donor> {
public async update(donor: Donor): Promise<Donor> {
donor.firstname = this.firstname;
donor.middlename = this.middlename;
donor.lastname = this.lastname;
donor.phone = this.phone;
donor.email = this.email;
donor.receiptNeeded = this.receiptNeeded;
donor.address = await this.getAddress();
if (this.receiptNeeded == true && this.address == null) {
if (!this.address) { donor.address.reset(); }
else { donor.address = this.address; }
Address.validate(donor.address);
if (this.receiptNeeded == true && Address.isValidAddress(donor.address) == false) {
throw new DonorReceiptAddressNeededError()
}
@@ -0,0 +1,27 @@
import { IsInt, IsPositive } from 'class-validator';
import { FixedDonation } from '../../entities/FixedDonation';
import { UpdateDonation } from './UpdateDonation';
/**
* This class is used to update a FixedDonation entity (via put request).
*/
export class UpdateFixedDonation extends UpdateDonation {
/**
* The updated donation's amount.
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amount: number;
/**
* Update a FixedDonation entity based on this.
* @param donation The donation that shall be updated.
*/
public async update(donation: FixedDonation): Promise<FixedDonation> {
donation.amount = this.amount;
donation.donor = await this.getDonor();
return donation;
}
}
@@ -0,0 +1,106 @@
import { IsEmail, IsInt, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact';
import { RunnerGroup } from '../../entities/RunnerGroup';
/**
* This class is used to update a GroupContact entity (via put request).
*/
export class UpdateGroupContact {
/**
* The updated contact's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The updated contact's middle name.
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The updated contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The updated contact's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* The updated contact's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The updated contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* The updated contacts's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* Get's all groups for this contact by their id's;
*/
public async getGroups(): Promise<RunnerGroup[]> {
if (!this.groups) { return null; }
let groups = new Array<RunnerGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
if (!found) { throw new RunnerGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
/**
* Updates a provided Donor entity based on this.
* @param contact the contact you want to update.
*/
public async update(contact: GroupContact): Promise<GroupContact> {
contact.firstname = this.firstname; GroupContact
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.phone = this.phone;
contact.email = this.email;
if (!this.address) { contact.address.reset(); }
else { contact.address = this.address; }
Address.validate(contact.address);
contact.groups = await this.getGroups();
return contact;
}
}
@@ -1,11 +1,11 @@
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
import { IsInt, IsNotEmpty, IsPositive } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { PermissionNeedsPrincipalError } from '../../errors/PermissionErrors';
import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../errors/PrincipalErrors';
import { Permission } from '../entities/Permission';
import { Principal } from '../entities/Principal';
import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
import { PermissionNeedsPrincipalError } from '../../../errors/PermissionErrors';
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
import { Permission } from '../../entities/Permission';
import { Principal } from '../../entities/Principal';
import { PermissionAction } from '../../enums/PermissionAction';
import { PermissionTarget } from '../../enums/PermissionTargets';
/**
* This class is used to update a Permission entity (via put request).
@@ -20,12 +20,11 @@ export class UpdatePermission {
id: number;
/**
* The updated permissions's principal.
* Just has to contain the principal's id -everything else won't be checked or changed.
* The updated permissions's principal's id.
*/
@IsObject()
@IsNotEmpty()
principal: Principal;
@IsInt()
@IsPositive()
principal: number;
/**
* The permissions's target.
@@ -42,7 +41,7 @@ export class UpdatePermission {
/**
* Updates a provided Permission entity based on this.
*/
public async updatePermission(permission: Permission): Promise<Permission> {
public async update(permission: Permission): Promise<Permission> {
permission.principal = await this.getPrincipal();
permission.target = this.target;
permission.action = this.action;
@@ -57,12 +56,8 @@ export class UpdatePermission {
if (this.principal === undefined || this.principal === null) {
throw new PermissionNeedsPrincipalError();
}
if (!isNaN(this.principal.id)) {
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal.id });
if (!principal) { throw new PrincipalNotFoundError(); }
return principal;
}
throw new PrincipalWrongTypeError();
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal });
if (!principal) { throw new PrincipalNotFoundError(); }
return principal;
}
}

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