Compare commits

..

177 Commits

Author SHA1 Message Date
dd9cb6d3ef Merge pull request 'Alpha Release 0.4.3' (#139) from dev into main
All checks were successful
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
23c732b690 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-02-07 12:40:54 +00:00
656d564baa 🚀Bumped version to v0.4.3
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-07 13:40:32 +01:00
f3f5cb462e 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-07 12:38:54 +00:00
9959172f2a Merge pull request 'Bugfix for @lfk/frontend/#43' (#138) from bugfix/118-encode_jwt_in_mail into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #138
2021-02-07 12:38:34 +00:00
8f0a396dd0 Bugfix for @lfk/frontend/#43
All checks were successful
continuous-integration/drone/pr Build is passing
2021-02-07 13:37:01 +01:00
a18d4d3cee 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-02 15:02:34 +00:00
390b36dfd4 Merge pull request 'Alpha Release 0.4.2' (#137) from dev into main
All checks were successful
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
3b718f3ce5 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-02-02 14:46:03 +00:00
321b20b073 🚀Bumped version to v0.4.2
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-02 15:45:41 +01:00
f7a0ec7174 🧾New changelog file version [CI SKIP] [skip ci] 2021-02-02 14:45:15 +00:00
110a84783e Merge pull request 'Imprint&Privacy Links feature/135-imprint_and_privacy' (#136) from feature/135-imprint_and_privacy into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #136
2021-02-02 14:45:00 +00:00
333e806da4 Added documentation about the new env vars to the readme
All checks were successful
continuous-integration/drone/pr Build is passing
ref #135
2021-02-02 15:10:04 +01:00
f4f621973a Added imprint and privacy to the api spec
ref #135
2021-02-02 15:07:33 +01:00
bcad691045 Added new url env vars to config
ref #135
2021-02-02 15:04:09 +01:00
74791df68b 📖New license file version [CI SKIP] [skip ci] 2021-01-30 17:13:12 +00:00
8425043099 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 17:12:25 +00:00
74b982afba fixed license-exporter call
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-30 18:12:09 +01:00
3aefa75412 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 16:00:16 +00:00
71cab4e836 Merge pull request 'Alpha Release 0.4.1' (#134) from dev into main
All checks were successful
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
4e10077901 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-30 15:54:03 +00:00
c32fa93673 🚀Bumped version to v0.4.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-30 16:53:38 +01:00
3d1baae0cc Dependency bump🔝 [skip ci] 2021-01-30 16:53:21 +01:00
94dd7963b7 Deleted useless file [ci skip] 2021-01-30 16:44:39 +01:00
7ba67b9dca 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 15:40:39 +00:00
6e5f1bd5ff Merge pull request 'Response object types feature/132-object_types' (#133) from feature/132-object_types into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #133
2021-01-30 15:40:24 +00:00
60ee6ebc1e Merge branch 'dev' into feature/132-object_types
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-30 15:32:20 +00:00
02295346da 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-30 15:27:16 +00:00
c4ea808e06 Merge pull request 'Alpha Release 0.4.0' (#131) from dev into main
All checks were successful
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
ff7406e71a Cleaned up realations regarding response classes
All checks were successful
continuous-integration/drone/pr Build is passing
ref #132
2021-01-30 16:19:42 +01:00
8dc2810c0c Adjusted tests for the new responseType parameter (part 3)
ref #132
2021-01-30 16:19:20 +01:00
ff8af090e3 Adjusted tests for the new responseType parameter (part 2)
ref #132
2021-01-30 15:39:39 +01:00
bcc15e4286 Adjusted tests for the new responseType parameter (part 1)
ref #132
2021-01-29 18:46:15 +01:00
2a87819486 Fixed typos and missing types
ref #132
2021-01-29 17:06:57 +01:00
9d5e486c6d Implemented the interface in all responses
refr #132
2021-01-29 17:06:43 +01:00
e44cc4c4cb Added a Response interface
ref #132
2021-01-29 16:49:19 +01:00
581ca5ff6c Added Responseobjecttype enum
ref #132
2021-01-29 16:45:23 +01:00
b972395ae8 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 16:54:23 +00:00
e5f4f6ee59 🚀Bumped version to v0.4.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-27 17:53:58 +01:00
fea4857685 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:52:45 +00:00
f9e75d06b8 Merge pull request 'Implemented testmail endpoint feature/124-testmail' (#130) from feature/124-testmail into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #130
2021-01-27 16:52:28 +00:00
38223b194b Merge branch 'dev' into feature/124-testmail
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 17:47:29 +01:00
09b24aa609 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:47:07 +00:00
348e6cdec7 Merge pull request 'Email Basics feature/118-emails' (#128) from feature/118-emails into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #128
2021-01-27 16:46:34 +00:00
bd1813a0e8 Merge branch 'dev' into feature/118-emails
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 16:45:17 +00:00
e07f258a31 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:44:56 +00:00
61bbeb0d8f Merge pull request 'Mail+Env documentation feature/123-mail_documentation' (#129) from feature/123-mail_documentation into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #129
2021-01-27 16:44:33 +00:00
650a55e586 Merge branch 'dev' into feature/123-mail_documentation
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 16:44:19 +00:00
2071c4db33 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-27 16:43:56 +00:00
80e606aa96 Merge branch 'dev' into feature/123-mail_documentation
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 16:43:54 +00:00
20f960ed67 Merge pull request 'Alpha Release 0.3.1' (#127) from dev into main
Some checks failed
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
e6fe8fcd58 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 16:32:04 +00:00
870fd47c83 Merge pull request 'new advanced endpoints feature/125-team_runner' (#126) from feature/125-team_runner into dev
All checks were successful
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
644045db44 Merge branch 'dev' into feature/125-team_runner
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 16:31:16 +00:00
8611fcb849 Merge branch 'dev' into feature/124-testmail
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-27 11:08:37 +00:00
08e6e59655 Removed the duplicate env copy/create from ci tests
All checks were successful
continuous-integration/drone/pr Build is passing
ref #124
2021-01-27 12:06:00 +01:00
ae74b3963f Added test mail sending test
All checks were successful
continuous-integration/drone/pr Build is passing
ref #124
2021-01-27 12:03:02 +01:00
54ed313342 Implemented the test-mail endpoint via a new mailcontroller
ref #124
2021-01-27 11:59:32 +01:00
ad4b903c25 Added a Mail permisssion target
ref #124
2021-01-27 11:53:33 +01:00
9bd7636a23 Added comments
ref #124
2021-01-27 11:37:46 +01:00
b94179e3ca Added a test mail sending function
ref #124
2021-01-27 11:28:27 +01:00
827002989e Added test mail templates
ref #124
2021-01-27 11:28:02 +01:00
eeff67c192 Merge branch 'dev' into feature/123-mail_documentation
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-26 19:42:59 +00:00
583a4bc0dd Changed order
All checks were successful
continuous-integration/drone/pr Build is passing
ref #123
2021-01-26 20:41:44 +01:00
53fcff77d0 Added a hint to ethereal.email
ref #123
2021-01-26 20:41:08 +01:00
1f0c842d9e Table fix
ref #123
2021-01-26 20:39:38 +01:00
13ccab5e28 Added documentation for the env vars
ref #123
2021-01-26 20:38:53 +01:00
b5018eb114 Added the basics about mail templates to the readme
ref #123
2021-01-26 20:26:25 +01:00
aedfcfcc83 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-26 19:15:59 +00:00
db0876015b 🚀Bumped version to v0.3.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 20:15:35 +01:00
69417e93c0 Added get runners by team test
All checks were successful
continuous-integration/drone/pr Build is passing
ref #125
2021-01-26 20:13:39 +01:00
f71a22f4dd Added get runners by org test
ref #125
2021-01-26 20:12:14 +01:00
570c34bed0 Created the organizations/runners endpoint
ref #125
2021-01-26 20:06:54 +01:00
7be2971a9e Created the runnerTeam/runners endpoint
ref #125
2021-01-26 19:57:35 +01:00
b92f633d68 Now also sending txt mail body
All checks were successful
continuous-integration/drone/pr Build is passing
ref #118
2021-01-26 18:45:19 +01:00
d3647e3399 Added a txt variant of the pw-reset mail
ref #118
2021-01-26 18:41:50 +01:00
389e423850 Cleaned up the replacements
ref #118
2021-01-26 18:41:21 +01:00
46af786516 Fixed missing app_url protocol
ref #118
2021-01-26 18:31:34 +01:00
b4c117b7dc Fixed wrong file location
ref #118
2021-01-26 18:31:17 +01:00
5cade25eeb Translated the pw reset mail to english
ref #118
2021-01-26 18:17:52 +01:00
fb77f4d798 Renamed the template
ref #118
2021-01-26 18:09:27 +01:00
c116338cd7 Added pw reset template provided by @philipp
ref #118
2021-01-26 18:09:00 +01:00
979d36ea91 Password reset now enforces email
ref #118
2021-01-26 18:07:56 +01:00
c43334bf96 The auth tests now use mail to identify the user
ref #118
2021-01-26 18:07:42 +01:00
71c4caae8b Removed bs console.log
ref #118
2021-01-26 17:55:41 +01:00
536de2a319 Implemented automatic ci env generation
ref #118
2021-01-26 17:54:25 +01:00
e26744b792 Implementes mail sending on pw reset request
ref #118
2021-01-26 17:35:03 +01:00
d02e9dec56 Removed tests working directly with the old pw-reset response
ref #118
2021-01-26 17:28:20 +01:00
637975305f Implemented a basic mailer with reset link sending
ref #118
2021-01-26 17:21:18 +01:00
c418603423 Added the first mail error
ref #118
2021-01-26 17:20:55 +01:00
78d2ac3027 Added nodemailer types
ref #118
2021-01-26 17:20:44 +01:00
470703c4de Added env vars
ref #118
2021-01-26 17:20:33 +01:00
e260e16d66 Merge branch 'feature/118-emails' of git.odit.services:lfk/backend into feature/118-emails 2021-01-26 16:10:26 +01:00
6b0155f014 Added a folder for the mail templates
ref #118
2021-01-26 16:10:24 +01:00
33890b544b Added a folder for the mail templates
ref #118
2021-01-26 16:10:02 +01:00
d7ea928714 Added mail env vars
ref #118
2021-01-26 16:09:11 +01:00
908ac4f1ce Added nodemailer dependecy
ref #118
2021-01-26 16:06:47 +01:00
cf012c0b7e Added a barebones class for handleing mail stuff
ref #118
2021-01-26 16:05:55 +01:00
71898d576c 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-24 17:58:01 +00:00
c964591839 Merge pull request 'Alpha Release 0.3.0' (#122) from dev into main
All checks were successful
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
cc4bf4451c 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-24 17:53:11 +00:00
7dbbd3780d Merge branch 'dev' of git.odit.services:lfk/backend into dev
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-24 18:52:36 +01:00
3697783e19 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-24 17:52:09 +00:00
161feaf364 Merge pull request 'OrganiZation rename feature/117-organization' (#121) from feature/117-organization into dev
Some checks failed
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
75e2a44c9c 🚀Bumped version to v0.3.0 2021-01-24 18:48:06 +01:00
cd7e9b86b4 Renamedpermisssions from *Organisation* to *Organization*📝
All checks were successful
continuous-integration/drone/pr Build is passing
ref #117
2021-01-24 18:43:29 +01:00
c6c643ecf1 Renamed files and classed from *Organisation* to *Organization*📝
ref #117
2021-01-24 18:40:46 +01:00
ef15d0d576 Changed organisation* to organization* in descriptions, comments and endoints ✏
ref #117
2021-01-24 18:34:15 +01:00
5660aecb50 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-22 14:13:45 +00:00
6a66dd803b Merge pull request 'Self service registration feature/112-selfservice_registration' (#120) from feature/112-selfservice_registration into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #120
2021-01-22 14:13:25 +00:00
b42f0722d7 Merge branch 'dev' into feature/112-selfservice_registration
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-21 20:02:23 +01:00
45c8bb83be Fixed tests testing for a old responseclass
ref #112
2021-01-21 20:01:44 +01:00
6469e3bc97 Fixed wrong error getting thrown
ref #112
2021-01-21 19:51:40 +01:00
10f98e9c99 Bugfix: turned old entity in response to responseclass
ref #112
2021-01-21 19:49:11 +01:00
e5b6f650b2 Added registration invalid company tests
ref #112
2021-01-21 19:48:45 +01:00
3b2ed3f0f2 Resolved missing relation
ref #112
2021-01-21 19:46:46 +01:00
20e102ec5c Added registration valid company tests
ref #112
2021-01-21 19:46:32 +01:00
5a003945ac Updated response schema error to a more fitting one
ref #112
2021-01-21 19:34:27 +01:00
29aeb046de Added registration invalid company tests
ref #112
2021-01-21 19:34:11 +01:00
72941da1cb Added registration valid citizentests
ref #112
2021-01-21 19:33:55 +01:00
81d2197a3e Added registration invalid citizen tests
ref #112
2021-01-21 19:22:26 +01:00
9dd9304a71 Citizen registration now returns tokens
ref #112
2021-01-21 19:17:40 +01:00
0c87906cc3 Added selfservice get positive test
ref #112
2021-01-21 19:17:25 +01:00
1227408407 Fixed fluctuating test bahaviour
ref #112
2021-01-21 19:10:59 +01:00
f8d7544517 Marked param as optional (default: false)
ref #112
2021-01-21 19:08:43 +01:00
a9843ed459 Updates old tests to the new ss-ktokens
ref #112
2021-01-21 19:08:21 +01:00
46f9503543 Fixed typo
ref #112
2021-01-21 18:45:15 +01:00
c5d0646c42 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-21 17:32:17 +00:00
b441658570 Merge pull request 'Alpha Release 0.2.1' (#119) from dev into main
All checks were successful
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
e95c457e44 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-21 17:15:47 +00:00
6de9d547b7 🚀Bumped version to v0.2.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-21 18:15:24 +01:00
3a93c9c078 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-21 17:14:56 +00:00
36d01a0a89 Merge pull request 'Runner scans endpoint feature/113-runner_scans' (#116) from feature/113-runner_scans into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #116
2021-01-21 17:14:41 +00:00
6434b4dfce Added check for empty token for runner self-service get
ref #112
2021-01-21 18:12:53 +01:00
e964a8ed44 Added self-service get invalid tests
ref #112
2021-01-21 18:12:29 +01:00
c39a59e54e Implemented runner selfservice token generation
ref #112
2021-01-21 18:03:48 +01:00
34c852b12a Specified uft-8 format for string
ref #112
2021-01-21 17:54:11 +01:00
7b00b19fce MAde uuid column unique
ref #112
2021-01-21 17:52:03 +01:00
ad446500f9 Implemented registration key generation
ref #112
2021-01-21 17:48:13 +01:00
d490247d1e Implemented a registration key for organisations
ref #112
2021-01-21 17:30:43 +01:00
dee36395a6 Citizen runners now have to provide an email address for verification
ref #112
2021-01-21 17:19:04 +01:00
6df195b6ec Created a citizenrunner selfservice create action
ref #112
2021-01-21 17:18:25 +01:00
946efef252 Updated Method of removeing the team of citizen runners
ref #112
2021-01-21 17:01:56 +01:00
73b1114883 Added openapi description
ref #112
2021-01-21 16:48:53 +01:00
1b5465bea8 Implemented the citizen runner self-registration endpoint
ref #112
2021-01-21 16:47:13 +01:00
5288c701c1 Implemented the basics for the runner selfservice registration endpoint
ref #112
2021-01-21 16:43:04 +01:00
10af1ba341 Implemented a runner selfservice registration creation action
ref #112
2021-01-21 16:40:47 +01:00
26dff4f418 Added get tests for the /runner/scans endpoint
All checks were successful
continuous-integration/drone/pr Build is passing
ref #113
2021-01-21 16:07:11 +01:00
b5f3dec93b Added a "onlyValid" query param
ref #113
2021-01-21 15:57:56 +01:00
a82fc0fb9e Added a /runners/id/scans endpoint
ref #113
2021-01-21 15:55:29 +01:00
e2ec0a3b64 Readme reorganisation [skip ci] 2021-01-21 15:43:11 +01:00
f4668b6e81 Added sqlite as to env.sample db of choice [skip ci] 2021-01-21 15:27:11 +01:00
d5281348b6 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-20 19:14:43 +00:00
1717df113e Merge pull request 'Runner selfservice info endpoint feature/111-runner_selfservic_info' (#115) from feature/111-runner_selfservic_info into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #115
2021-01-20 19:14:19 +00:00
0355bdbbab Merge branch 'dev' into feature/111-runner_selfservic_info
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-20 19:13:18 +00:00
02677de5c0 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-20 19:13:07 +00:00
886c1092d6 Merge pull request 'Implemented more seeding feature/110-seeding' (#114) from feature/110-seeding into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #114
2021-01-20 19:12:49 +00:00
191569792c Updated the openapi description
All checks were successful
continuous-integration/drone/pr Build is passing
ref #111
2021-01-20 20:07:16 +01:00
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
4ee807973e Fixed wrong amount calculation
ref #111
2021-01-20 20:02:30 +01:00
c5f7cb2c68 Beautified import
ref #111
2021-01-20 19:44:24 +01:00
88a7089289 Created a donation runner response class for the runner selfservice
ref #111
2021-01-20 19:43:53 +01:00
b89f7ac1b4 Created a donation respoinse class for the runner selfservice
ref #111
2021-01-20 19:43:20 +01:00
8079769881 Implemented a method for getting the runner object from a jwt
ref #110
2021-01-20 19:20:08 +01:00
2274b476d6 Added barebones controller for the runner info selfservice
ref #111
2021-01-20 19:05:59 +01:00
e12aedd1aa Fixed the bool converter for null values
All checks were successful
continuous-integration/drone/pr Build is passing
ref #110
2021-01-20 18:28:41 +01:00
434aaf6136 Merge branch 'dev' into feature/110-seeding
Some checks failed
continuous-integration/drone/pr Build is failing
2021-01-20 17:09:14 +00:00
d8b6669d12 📖New license file version [CI SKIP] [skip ci] 2021-01-20 17:07:14 +00:00
7bc603028d The data seeding now only get's triggered on the first time thx to using the key-value
Some checks failed
continuous-integration/drone/pr Build is failing
ref #110
2021-01-20 18:02:08 +01:00
c18012f65a Added bool conversion for testdata seeding env var
ref #110
2021-01-20 17:59:33 +01:00
b15967ff31 Added key-value like db table for config flags
ref #110
2021-01-20 17:58:28 +01:00
2db6510a8a Added a citizen org seeder
ref #110
2021-01-20 17:58:11 +01:00
1837336865 Now creating a test contact
ref #110
2021-01-20 17:38:34 +01:00
eab0e634a2 Now also seeding runners to the test org
ref #110
2021-01-20 17:34:53 +01:00
8870ebdb5e SEED_TEST_DATA is now false by default
ref #110
2021-01-20 17:33:31 +01:00
9df9d9ae80 Added a seeder for runner test data
ref #110
2021-01-20 17:26:04 +01:00
67ba489fe2 Added a config option for test data seeding
ref #110
2021-01-20 17:25:46 +01:00
123 changed files with 3484 additions and 6189 deletions

View File

@@ -9,7 +9,6 @@ steps:
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout $DRONE_SOURCE_BRANCH
- mv .env.ci .env
- name: run tests
image: node:latest
commands:

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=DE
POSTALCODE_COUNTRYCODE=DE
SEED_TEST_DATA=false

View File

@@ -2,11 +2,206 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v0.4.3](https://git.odit.services/lfk/backend/compare/v0.4.2...v0.4.3)
- 🚀Bumped version to v0.4.3 [`656d564`](https://git.odit.services/lfk/backend/commit/656d564baa8c8bf1f63523b0301ad9ff23e08aa4)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f3f5cb4`](https://git.odit.services/lfk/backend/commit/f3f5cb462e4ecf932ad55eb519815222b4e5dd17)
- Merge pull request 'Bugfix for @lfk/frontend/#43' (#138) from bugfix/118-encode_jwt_in_mail into dev [`9959172`](https://git.odit.services/lfk/backend/commit/9959172f2ae11cbb7a2b8e97bad432956fc70a80)
- Bugfix for @lfk/frontend/#43 [`8f0a396`](https://git.odit.services/lfk/backend/commit/8f0a396dd07937fb7ccfb345d1acbac86eb5d9bb)
- 🧾New changelog file version [CI SKIP] [skip ci] [`a18d4d3`](https://git.odit.services/lfk/backend/commit/a18d4d3cee58f8eb9dc428b051a2394bd3ece5c2)
#### [v0.4.2](https://git.odit.services/lfk/backend/compare/v0.4.1...v0.4.2)
> 2 February 2021
- Merge pull request 'Alpha Release 0.4.2' (#137) from dev into main [`390b36d`](https://git.odit.services/lfk/backend/commit/390b36dfd46cf8829126581bd62dd3d4ce8558fa)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3b718f3`](https://git.odit.services/lfk/backend/commit/3b718f3ce58f12007b6068e5db00a00dbe1c5398)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f7a0ec7`](https://git.odit.services/lfk/backend/commit/f7a0ec7174521b1863a4bc58c7ff2b86cafdee66)
- 🚀Bumped version to v0.4.2 [`321b20b`](https://git.odit.services/lfk/backend/commit/321b20b073b6debd605a92544779d0dfc0449f10)
- Merge pull request 'Imprint&Privacy Links feature/135-imprint_and_privacy' (#136) from feature/135-imprint_and_privacy into dev [`110a847`](https://git.odit.services/lfk/backend/commit/110a84783e023407cbcf81506deb7cc204db9659)
- 📖New license file version [CI SKIP] [skip ci] [`74791df`](https://git.odit.services/lfk/backend/commit/74791df68b40355e1d1a1f7f5ae4f64422571dc9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8425043`](https://git.odit.services/lfk/backend/commit/84250430996920ada15af23b68756daba8f99257)
- Added new url env vars to config [`bcad691`](https://git.odit.services/lfk/backend/commit/bcad691045d00c9630bedb0936c123610b655946)
- fixed license-exporter call [`74b982a`](https://git.odit.services/lfk/backend/commit/74b982afba3ec82a038c4748420920732fe32a51)
- Added documentation about the new env vars to the readme [`333e806`](https://git.odit.services/lfk/backend/commit/333e806da42d7654e2b9fc13abae984726c689e7)
- Added imprint and privacy to the api spec [`f4f6219`](https://git.odit.services/lfk/backend/commit/f4f621973aa98645dee3d43252bb18f125087c54)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3aefa75`](https://git.odit.services/lfk/backend/commit/3aefa754128fc58a8200b280ee036c49cdaaac4a)
#### [v0.4.1](https://git.odit.services/lfk/backend/compare/v0.4.0...v0.4.1)
> 30 January 2021
- Merge pull request 'Alpha Release 0.4.1' (#134) from dev into main [`71cab4e`](https://git.odit.services/lfk/backend/commit/71cab4e836dca2e2072b62676337f5f90796f105)
- Deleted useless file [ci skip] [`94dd796`](https://git.odit.services/lfk/backend/commit/94dd7963b73d98089f086175ed79002f59195c26)
- Implemented the interface in all responses [`9d5e486`](https://git.odit.services/lfk/backend/commit/9d5e486c6ddb4db87d36409fbd8bca1bf9659e9f)
- Adjusted tests for the new responseType parameter (part 1) [`bcc15e4`](https://git.odit.services/lfk/backend/commit/bcc15e42863b641b97cd03440f141332e112c889)
- Cleaned up realations regarding response classes [`ff7406e`](https://git.odit.services/lfk/backend/commit/ff7406e71a4a76670d381b415bfb66f602e1206b)
- Added Responseobjecttype enum [`581ca5f`](https://git.odit.services/lfk/backend/commit/581ca5ff6c67ed1c701c06532671441293ee0706)
- 🧾New changelog file version [CI SKIP] [skip ci] [`0229534`](https://git.odit.services/lfk/backend/commit/02295346dad109745faa492dc968abbc98522804)
- Dependency bump🔝 [skip ci] [`3d1baae`](https://git.odit.services/lfk/backend/commit/3d1baae0cce26b0c1f6948778b15f4b0097a77a9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`7ba67b9`](https://git.odit.services/lfk/backend/commit/7ba67b9dcab7692c8c1e548ccbc3895eb084eedd)
- Adjusted tests for the new responseType parameter (part 3) [`8dc2810`](https://git.odit.services/lfk/backend/commit/8dc2810c0c4097e26bf1b517bb9d0b102494f6c1)
- Added a Response interface [`e44cc4c`](https://git.odit.services/lfk/backend/commit/e44cc4c4cbecdf7c8d90f0af73fffd8b01eba61e)
- 🧾New changelog file version [CI SKIP] [skip ci] [`4e10077`](https://git.odit.services/lfk/backend/commit/4e100779017c5ce542b59cf2156bd6ab5fdceed2)
- Adjusted tests for the new responseType parameter (part 2) [`ff8af09`](https://git.odit.services/lfk/backend/commit/ff8af090e3ea1760fb6110284cff870a0b4cbf84)
- 🚀Bumped version to v0.4.1 [`c32fa93`](https://git.odit.services/lfk/backend/commit/c32fa93673010ab18e009ab24fc139ed4f67310e)
- Merge pull request 'Response object types feature/132-object_types' (#133) from feature/132-object_types into dev [`6e5f1bd`](https://git.odit.services/lfk/backend/commit/6e5f1bd5ff217a88ec2dfaf154e5e26dee588e12)
- Fixed typos and missing types [`2a87819`](https://git.odit.services/lfk/backend/commit/2a878194865d406123805659c011c329c955f669)
#### [v0.4.0](https://git.odit.services/lfk/backend/compare/v0.3.1...v0.4.0)
> 30 January 2021
- Merge pull request 'Alpha Release 0.4.0' (#131) from dev into main [`c4ea808`](https://git.odit.services/lfk/backend/commit/c4ea808e06bdc68add5636346f99a7d292852ab0)
- Added pw reset template provided by @philipp [`c116338`](https://git.odit.services/lfk/backend/commit/c116338cd74cf726362f8fa0ae5eea7ec9fabac4)
- Added test mail templates [`8270029`](https://git.odit.services/lfk/backend/commit/827002989ee6e3f0d776b5b55b8fb6d65f10c898)
- 🧾New changelog file version [CI SKIP] [skip ci] [`09b24aa`](https://git.odit.services/lfk/backend/commit/09b24aa6094a980debf4428a1c32583cdb7b606f)
- Implemented automatic ci env generation [`536de2a`](https://git.odit.services/lfk/backend/commit/536de2a3199b1befed54b6fe520a2e3fcefe0d90)
- Implemented a basic mailer with reset link sending [`6379753`](https://git.odit.services/lfk/backend/commit/637975305f1adf9bf505507790638cf1e229cfb1)
- Table fix [`1f0c842`](https://git.odit.services/lfk/backend/commit/1f0c842d9e086456f1ae0f6908e474258a04beb4)
- Implemented the test-mail endpoint via a new mailcontroller [`54ed313`](https://git.odit.services/lfk/backend/commit/54ed313342a72b029b9545bc5ea193e3f0c2166d)
- Added documentation for the env vars [`13ccab5`](https://git.odit.services/lfk/backend/commit/13ccab5e289d0a629cefb7fe281a85a46058ae97)
- Added comments [`9bd7636`](https://git.odit.services/lfk/backend/commit/9bd7636a23b5a9662ea2b965d2a2407727a188fb)
- Added test mail sending test [`ae74b39`](https://git.odit.services/lfk/backend/commit/ae74b3963fddb847aed4a828031b93b26cf551db)
- Password reset now enforces email [`979d36e`](https://git.odit.services/lfk/backend/commit/979d36ea9147dc575e9e989f6833388828285176)
- Implementes mail sending on pw reset request [`e26744b`](https://git.odit.services/lfk/backend/commit/e26744b7925d32d65ef4cc3911651758cfc9274f)
- Added a txt variant of the pw-reset mail [`d3647e3`](https://git.odit.services/lfk/backend/commit/d3647e339990d989dbca4d91aa8c3fe5789dd24a)
- Changed order [`583a4bc`](https://git.odit.services/lfk/backend/commit/583a4bc0dd0de8026bb2eb6a9b0c31f59344e813)
- 🧾New changelog file version [CI SKIP] [skip ci] [`fea4857`](https://git.odit.services/lfk/backend/commit/fea485768570eb5de2bbd2783e339794a67db2de)
- Translated the pw reset mail to english [`5cade25`](https://git.odit.services/lfk/backend/commit/5cade25eeb137eb5890b3fd450646acfbdff2e8b)
- The auth tests now use mail to identify the user [`c43334b`](https://git.odit.services/lfk/backend/commit/c43334bf96901bfd5116301ff7cf4b2ae1dfcbd3)
- 🧾New changelog file version [CI SKIP] [skip ci] [`e07f258`](https://git.odit.services/lfk/backend/commit/e07f258a315898d1183c311e7fcd8f65a415504c)
- 🧾New changelog file version [CI SKIP] [skip ci] [`b972395`](https://git.odit.services/lfk/backend/commit/b972395ae8bb06184baa56296a3b8bfc16ae96a7)
- 🚀Bumped version to v0.4.0 [`e5f4f6e`](https://git.odit.services/lfk/backend/commit/e5f4f6ee590e0885d6eef9151ce7eb76578b70ca)
- Merge pull request 'Implemented testmail endpoint feature/124-testmail' (#130) from feature/124-testmail into dev [`f9e75d0`](https://git.odit.services/lfk/backend/commit/f9e75d06b8ee8ff79f60fb384cb2c35ccf19811d)
- Merge pull request 'Email Basics feature/118-emails' (#128) from feature/118-emails into dev [`348e6cd`](https://git.odit.services/lfk/backend/commit/348e6cdec7411345953243edfb5322a17ad7614d)
- Merge pull request 'Mail+Env documentation feature/123-mail_documentation' (#129) from feature/123-mail_documentation into dev [`61bbeb0`](https://git.odit.services/lfk/backend/commit/61bbeb0d8f3fd6bfafb65bd11eb4c076a27b4a53)
- Added a test mail sending function [`b94179e`](https://git.odit.services/lfk/backend/commit/b94179e3caaf4be0654ca3372f57a490fb32e208)
- Added the first mail error [`c418603`](https://git.odit.services/lfk/backend/commit/c4186034233a296b5971fbef16e7ef6809fbac51)
- Now also sending txt mail body [`b92f633`](https://git.odit.services/lfk/backend/commit/b92f633d68604636cecc5e9fdd0d6990b9cb83fe)
- Removed tests working directly with the old pw-reset response [`d02e9de`](https://git.odit.services/lfk/backend/commit/d02e9dec5637aedefdf2ed3cd2c6d73216b6464b)
- Added the basics about mail templates to the readme [`b5018eb`](https://git.odit.services/lfk/backend/commit/b5018eb11492884db9f4ec969c767c3cce53f105)
- Cleaned up the replacements [`389e423`](https://git.odit.services/lfk/backend/commit/389e423850d68a5fe440b62413a6c662353ac9c6)
- Added mail env vars [`d7ea928`](https://git.odit.services/lfk/backend/commit/d7ea928714f94814695cbd2815c8730df58033f6)
- Added a barebones class for handleing mail stuff [`cf012c0`](https://git.odit.services/lfk/backend/commit/cf012c0b7efffb81b03497a04b0fdad0423c72f7)
- Added a Mail permisssion target [`ad4b903`](https://git.odit.services/lfk/backend/commit/ad4b903c258820f14df28d56b12e099075ca7d78)
- Added env vars [`470703c`](https://git.odit.services/lfk/backend/commit/470703c4de954da94726879becd57986b59e1f69)
- 🧾New changelog file version [CI SKIP] [skip ci] [`2071c4d`](https://git.odit.services/lfk/backend/commit/2071c4db33bbb9fd41ef650b409cac789732225f)
- Added a hint to ethereal.email [`53fcff7`](https://git.odit.services/lfk/backend/commit/53fcff77d00fc2b205ada0bcee7bdfe83d94a9f4)
- Fixed missing app_url protocol [`46af786`](https://git.odit.services/lfk/backend/commit/46af7865165bbfb97ed3e6cdfef15dfb72add611)
- Removed the duplicate env copy/create from ci tests [`08e6e59`](https://git.odit.services/lfk/backend/commit/08e6e5965544906f5033f2080166bddc37cc30c7)
- Removed bs console.log [`71c4caa`](https://git.odit.services/lfk/backend/commit/71c4caae8ba67e253d893409b3c5c3a39b08060a)
- Added nodemailer types [`78d2ac3`](https://git.odit.services/lfk/backend/commit/78d2ac3027f7109161ee36e9b3dda628a7039468)
- Added nodemailer dependecy [`908ac4f`](https://git.odit.services/lfk/backend/commit/908ac4f1ce9d78749268353c668e67b57eae6f94)
- Fixed wrong file location [`b4c117b`](https://git.odit.services/lfk/backend/commit/b4c117b7dc326d212598b6e720d0a422134e383d)
- Renamed the template [`fb77f4d`](https://git.odit.services/lfk/backend/commit/fb77f4d798550fdb6fe6b2c8a81198db0328f71e)
- Added a folder for the mail templates [`6b0155f`](https://git.odit.services/lfk/backend/commit/6b0155f01464f5ef73ab679b9e3219743e9db66b)
- Added a folder for the mail templates [`33890b5`](https://git.odit.services/lfk/backend/commit/33890b544b77272ab1c4797e91375d24568eae58)
#### [v0.3.1](https://git.odit.services/lfk/backend/compare/v0.3.0...v0.3.1)
> 27 January 2021
- Merge pull request 'Alpha Release 0.3.1' (#127) from dev into main [`20f960e`](https://git.odit.services/lfk/backend/commit/20f960ed6700fe58c0556895e6485d26c4a4f5e2)
- 🧾New changelog file version [CI SKIP] [skip ci] [`e6fe8fc`](https://git.odit.services/lfk/backend/commit/e6fe8fcd587751392970d3ee412559b4c1d60f21)
- Merge pull request 'new advanced endpoints feature/125-team_runner' (#126) from feature/125-team_runner into dev [`870fd47`](https://git.odit.services/lfk/backend/commit/870fd47c83389345d7b24a15df6a4e61e930d140)
- Added get runners by team test [`69417e9`](https://git.odit.services/lfk/backend/commit/69417e93c081422561db1e211b12f32e539010ce)
- 🧾New changelog file version [CI SKIP] [skip ci] [`71898d5`](https://git.odit.services/lfk/backend/commit/71898d576c2620d2f2e2b4336e62f1d04a443201)
- Created the organizations/runners endpoint [`570c34b`](https://git.odit.services/lfk/backend/commit/570c34bed04e359f389a8f427486bf92891f1dcb)
- Created the runnerTeam/runners endpoint [`7be2971`](https://git.odit.services/lfk/backend/commit/7be2971a9e02bf0e784f7fe5cdd82afbbbf7f854)
- 🧾New changelog file version [CI SKIP] [skip ci] [`aedfcfc`](https://git.odit.services/lfk/backend/commit/aedfcfcc8359afd7dba4fa5e515e8e77fbb3fc6e)
- Added get runners by org test [`f71a22f`](https://git.odit.services/lfk/backend/commit/f71a22f4dd9bf14d39ced91908f6f6a5d8395e56)
- 🚀Bumped version to v0.3.1 [`db08760`](https://git.odit.services/lfk/backend/commit/db0876015bf0599dabb21357f172735888c79aa8)
#### [v0.3.0](https://git.odit.services/lfk/backend/compare/v0.2.1...v0.3.0)
> 24 January 2021
- Merge pull request 'Alpha Release 0.3.0' (#122) from dev into main [`c964591`](https://git.odit.services/lfk/backend/commit/c9645918398e62a80341d038b6b6b5c60ee4b1c7)
- Renamed files and classed from *Organisation* to *Organization*📝 [`c6c643e`](https://git.odit.services/lfk/backend/commit/c6c643ecf125f5fdf58b105f97efad32e8545dd4)
- Changed organisation* to organization* in descriptions, comments and endoints ✏ [`ef15d0d`](https://git.odit.services/lfk/backend/commit/ef15d0d57619d79e014e0c0331965ca2bc664254)
- Added registration invalid citizen tests [`81d2197`](https://git.odit.services/lfk/backend/commit/81d2197a3e978ca43bc79720c32d75f8490f08df)
- Implemented registration key generation [`ad44650`](https://git.odit.services/lfk/backend/commit/ad446500f90f945aadc3510377bcfd2123440da0)
- Implemented a runner selfservice registration creation action [`10af1ba`](https://git.odit.services/lfk/backend/commit/10af1ba34148c992e94fa580e1c0210bfaea5112)
- Created a citizenrunner selfservice create action [`6df195b`](https://git.odit.services/lfk/backend/commit/6df195b6ec5fde27f84cbe54992558715b843b87)
- 🧾New changelog file version [CI SKIP] [skip ci] [`5660aec`](https://git.odit.services/lfk/backend/commit/5660aecb50c0e6dd538850c6375d2f692fb7a1f2)
- Implemented a registration key for organisations [`d490247`](https://git.odit.services/lfk/backend/commit/d490247d1e337a680b385d2115e82f79ba54a601)
- Updates old tests to the new ss-ktokens [`a9843ed`](https://git.odit.services/lfk/backend/commit/a9843ed4598485e6e3d18e78b876b6e000ea6e38)
- Added self-service get invalid tests [`e964a8e`](https://git.odit.services/lfk/backend/commit/e964a8ed44109899516c4d3b0dc35fe4990107a1)
- Implemented runner selfservice token generation [`c39a59e`](https://git.odit.services/lfk/backend/commit/c39a59e54ef0b1cc891684283422805af38969e3)
- 🧾New changelog file version [CI SKIP] [skip ci] [`cc4bf44`](https://git.odit.services/lfk/backend/commit/cc4bf4451cf4eac0e0f1bab02119c2dd5e300f48)
- Renamedpermisssions from *Organisation* to *Organization*📝 [`cd7e9b8`](https://git.odit.services/lfk/backend/commit/cd7e9b86b4b9d3e1ef0312f6fff436fcef4a5c94)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3697783`](https://git.odit.services/lfk/backend/commit/3697783e190e36f1168132d75da1eca7be27b4f6)
- Merge pull request 'OrganiZation rename feature/117-organization' (#121) from feature/117-organization into dev [`161feaf`](https://git.odit.services/lfk/backend/commit/161feaf364195c7b85041e06cdefef72d75b8951)
- Added registration valid company tests [`20e102e`](https://git.odit.services/lfk/backend/commit/20e102ec5c52c6a01144d064c96b8b2bd1ccfd00)
- Added registration invalid company tests [`29aeb04`](https://git.odit.services/lfk/backend/commit/29aeb046de769e37fd7257507e36337237697ea0)
- Added registration valid citizentests [`72941da`](https://git.odit.services/lfk/backend/commit/72941da1cb1c31fd6bfcba2ee3704f34bdd73ef0)
- Citizen runners now have to provide an email address for verification [`dee3639`](https://git.odit.services/lfk/backend/commit/dee36395a69da6c5e1bc4e94bd705b0418a6a3ff)
- Implemented the basics for the runner selfservice registration endpoint [`5288c70`](https://git.odit.services/lfk/backend/commit/5288c701c1ac880f3c8b7ece01457aae4bac87d7)
- Added selfservice get positive test [`0c87906`](https://git.odit.services/lfk/backend/commit/0c87906cc3fa5feaf1d7c186f68d978d4ed7fa8e)
- Fixed tests testing for a old responseclass [`45c8bb8`](https://git.odit.services/lfk/backend/commit/45c8bb83be0814bed8856a617de435f4694db76c)
- Fixed typo [`46f9503`](https://git.odit.services/lfk/backend/commit/46f9503543ee011d0780caeefa95748e4be45f58)
- 🧾New changelog file version [CI SKIP] [skip ci] [`c5d0646`](https://git.odit.services/lfk/backend/commit/c5d0646c425f83689bffd862edd568ca0c1b5ad8)
- Added registration invalid company tests [`e5b6f65`](https://git.odit.services/lfk/backend/commit/e5b6f650b25af776a543f7f9c7ccf0000bf07a66)
- Marked param as optional (default: false) [`f8d7544`](https://git.odit.services/lfk/backend/commit/f8d754451769e8361ec2367df7bc586f8fffe8eb)
- Bugfix: turned old entity in response to responseclass [`10f98e9`](https://git.odit.services/lfk/backend/commit/10f98e9c992b1fb45334e65082d7c47af214b6ac)
- Resolved missing relation [`3b2ed3f`](https://git.odit.services/lfk/backend/commit/3b2ed3f0f2557dccddf6ba656b201c196cc24bf0)
- Fixed fluctuating test bahaviour [`1227408`](https://git.odit.services/lfk/backend/commit/1227408407ac66b9689446c1f318b453a9d45069)
- 🚀Bumped version to v0.3.0 [`75e2a44`](https://git.odit.services/lfk/backend/commit/75e2a44c9c7f720d1a5a20611b305e5d56a9155b)
- Merge pull request 'Self service registration feature/112-selfservice_registration' (#120) from feature/112-selfservice_registration into dev [`6a66dd8`](https://git.odit.services/lfk/backend/commit/6a66dd803becb59172e8645c6b55898fb4b4c0b5)
- Implemented the citizen runner self-registration endpoint [`1b5465b`](https://git.odit.services/lfk/backend/commit/1b5465bea810f59cbf8bb1a3e82c062176f79c49)
- Citizen registration now returns tokens [`9dd9304`](https://git.odit.services/lfk/backend/commit/9dd9304a71eae94978710cf9db82dc030ca7a37d)
- Fixed wrong error getting thrown [`6469e3b`](https://git.odit.services/lfk/backend/commit/6469e3bc976c589b42fbac4a95bbee84f67244ee)
- Updated response schema error to a more fitting one [`5a00394`](https://git.odit.services/lfk/backend/commit/5a003945ac7d1a8d446c1bcc8007523b675ab6f5)
- Added check for empty token for runner self-service get [`6434b4d`](https://git.odit.services/lfk/backend/commit/6434b4dfce2da307d66ec5670d570d66ea8af8f8)
- Specified uft-8 format for string [`34c852b`](https://git.odit.services/lfk/backend/commit/34c852b12aaa88e64efa1e0575361c09652e22e1)
- MAde uuid column unique [`7b00b19`](https://git.odit.services/lfk/backend/commit/7b00b19fce3189069bcdbe0d2bcf91322506eb2b)
- Updated Method of removeing the team of citizen runners [`946efef`](https://git.odit.services/lfk/backend/commit/946efef2523c5ceb627b5c465343422cd985832d)
- Added openapi description [`73b1114`](https://git.odit.services/lfk/backend/commit/73b1114883ed2b87ef523130de4c5ca90a11c856)
#### [v0.2.1](https://git.odit.services/lfk/backend/compare/v0.2.0...v0.2.1)
> 21 January 2021
- Merge pull request 'Alpha Release 0.2.1' (#119) from dev into main [`b441658`](https://git.odit.services/lfk/backend/commit/b44165857016c3ecdf0dffe8d324d572524d10ed)
- Created a donation runner response class for the runner selfservice [`88a7089`](https://git.odit.services/lfk/backend/commit/88a7089289e35be4468cb952b311fcb15c54c5a1)
- Readme reorganisation [skip ci] [`e2ec0a3`](https://git.odit.services/lfk/backend/commit/e2ec0a3b64a7388ae85d557dfb66354d70cd1b72)
- Added a seeder for runner test data [`9df9d9a`](https://git.odit.services/lfk/backend/commit/9df9d9ae80277d5ccc753639badb48c4afb13088)
- Created a donation respoinse class for the runner selfservice [`b89f7ac`](https://git.odit.services/lfk/backend/commit/b89f7ac1b4ddd6e53e6e2e8330c1fa2170b48591)
- Added barebones controller for the runner info selfservice [`2274b47`](https://git.odit.services/lfk/backend/commit/2274b476d6caa1de91bb13b6944f8dc233cf446e)
- Implemented a method for getting the runner object from a jwt [`8079769`](https://git.odit.services/lfk/backend/commit/80797698818f456c7746523d5a4f66267fdab10d)
- Added key-value like db table for config flags [`b15967f`](https://git.odit.services/lfk/backend/commit/b15967ff3162e9fe3a634a6f4fc5669f2314cc21)
- Added a /runners/id/scans endpoint [`a82fc0f`](https://git.odit.services/lfk/backend/commit/a82fc0fb9e9c3cbdc6be299b27164c0811e58775)
- Now creating a test contact [`1837336`](https://git.odit.services/lfk/backend/commit/1837336865893ca39d3bc628ff3c57e018a8555d)
- 🧾New changelog file version [CI SKIP] [skip ci] [`02677de`](https://git.odit.services/lfk/backend/commit/02677de5c07b2ac5dcff5567655130ba1b1d48cf)
- The data seeding now only get's triggered on the first time thx to using the key-value [`7bc6030`](https://git.odit.services/lfk/backend/commit/7bc603028dc60d26ffc5327868afbce512966d4d)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3a93c9c`](https://git.odit.services/lfk/backend/commit/3a93c9c078af38ba837b55bf4590867dfd401955)
- Added a "onlyValid" query param [`b5f3dec`](https://git.odit.services/lfk/backend/commit/b5f3dec93bfe4180abbe9ce74094cb1269d0e686)
- Added a citizen org seeder [`2db6510`](https://git.odit.services/lfk/backend/commit/2db6510a8ad83300b286a3bd35ca4db103da72d1)
- 🧾New changelog file version [CI SKIP] [skip ci] [`d528134`](https://git.odit.services/lfk/backend/commit/d5281348b6f3bd6f2e6936ee4497860699b8c3c6)
- 📖New license file version [CI SKIP] [skip ci] [`d8b6669`](https://git.odit.services/lfk/backend/commit/d8b6669d126e64d9e434b5f841ae17a02117822b)
- 🧾New changelog file version [CI SKIP] [skip ci] [`e95c457`](https://git.odit.services/lfk/backend/commit/e95c457e444e9d2e99c2af8b6ee369c36d4ca8ea)
- Beautified import [`c5f7cb2`](https://git.odit.services/lfk/backend/commit/c5f7cb2c68dbee0ab1e0361754f4d4b876666c82)
- 🚀Bumped version to v0.2.1 [`6de9d54`](https://git.odit.services/lfk/backend/commit/6de9d547b736c4538dac5254353d483576337290)
- Merge pull request 'Runner scans endpoint feature/113-runner_scans' (#116) from feature/113-runner_scans into dev [`36d01a0`](https://git.odit.services/lfk/backend/commit/36d01a0a890eb74428679ec6c4fcb14708aaa9fe)
- Added get tests for the /runner/scans endpoint [`26dff4f`](https://git.odit.services/lfk/backend/commit/26dff4f41829e8571231aff3c5d0e3a7c53559d8)
- Implemented the get part of the runner selfservice (no jwts are availdable yet (tm) [`da1fe34`](https://git.odit.services/lfk/backend/commit/da1fe34249a741115c1aeedcade16c5c852e896b)
- Fixed the bool converter for null values [`e12aedd`](https://git.odit.services/lfk/backend/commit/e12aedd1aad6de1f934e9593dda4607a303b2eb5)
- Added a config option for test data seeding [`67ba489`](https://git.odit.services/lfk/backend/commit/67ba489fe2f2a2706d640a668cd0e675ded6a7df)
- SEED_TEST_DATA is now false by default [`8870ebd`](https://git.odit.services/lfk/backend/commit/8870ebdb5e6d9045222440abc2c047929a74b520)
- Added sqlite as to env.sample db of choice [skip ci] [`f4668b6`](https://git.odit.services/lfk/backend/commit/f4668b6e81d7aeac62e24291ffcb39b00ea44aac)
- Merge pull request 'Runner selfservice info endpoint feature/111-runner_selfservic_info' (#115) from feature/111-runner_selfservic_info into dev [`1717df1`](https://git.odit.services/lfk/backend/commit/1717df113edeab2d2628041ee1eccc27380fd379)
- Merge pull request 'Implemented more seeding feature/110-seeding' (#114) from feature/110-seeding into dev [`886c109`](https://git.odit.services/lfk/backend/commit/886c1092d60f8e39357e3b841ed01bb082ede2c4)
- Updated the openapi description [`1915697`](https://git.odit.services/lfk/backend/commit/191569792c9a5cee93718555bba4e7679e4391af)
- Fixed wrong amount calculation [`4ee8079`](https://git.odit.services/lfk/backend/commit/4ee807973e1995681ec549f7c482bc5514a6ec55)
- Added bool conversion for testdata seeding env var [`c18012f`](https://git.odit.services/lfk/backend/commit/c18012f65a704e07acd56870c9ed9f6d06cf97a9)
- Now also seeding runners to the test org [`eab0e63`](https://git.odit.services/lfk/backend/commit/eab0e634a26c1a80e7fa2ccb9dc368f0760b2fd8)
#### [v0.2.0](https://git.odit.services/lfk/backend/compare/v0.1.1...v0.2.0)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8960aa5`](https://git.odit.services/lfk/backend/commit/8960aa5545ddeb57d4ef42c21c0ca6001dfeaea9)
- 🚀Bumped version to v0.2.0 [`ddafd90`](https://git.odit.services/lfk/backend/commit/ddafd90d3e41fb9ee37172a8306c30d8483dfe2c)
- Merge pull request 'Implemented group contacts feature/104-contacts' (#108) from feature/104-contacts into dev [`a0c2b5a`](https://git.odit.services/lfk/backend/commit/a0c2b5ade8d198ec16d33b39e47205e8b03a669f)
> 20 January 2021
- Merge pull request 'Alpha Release 0.2.0' (#109) from dev into main [`dd3d93e`](https://git.odit.services/lfk/backend/commit/dd3d93edc7db7ca7f133cb2d8f60c3eaf30bcbf0)
- Updated contact update tests [`c3d008e`](https://git.odit.services/lfk/backend/commit/c3d008ec0ff92f80addbdb93ffc1fa2b3278a8a6)
- Added contact delete tests [`dd7e5da`](https://git.odit.services/lfk/backend/commit/dd7e5dae368a8decd79357f658dda2164fa6f1e7)
- Added contact add valid tests [`e165f01`](https://git.odit.services/lfk/backend/commit/e165f019307e7745357493eacf3e2fa31538122b)
@@ -29,6 +224,7 @@ All notable changes to this project will be documented in this file. Dates are d
- Implemented contact updateing [`28fb983`](https://git.odit.services/lfk/backend/commit/28fb9834e18bde012c5b51cc49a39585d20f7cc1)
- Fixed key null constraint [`de82437`](https://git.odit.services/lfk/backend/commit/de824375d3a1da6ee4d78ea39b7da66fc05f2a02)
- Implemented contact posting [`11af9c0`](https://git.odit.services/lfk/backend/commit/11af9c02d977dcd6919652256dbdb9fd5438cabd)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8960aa5`](https://git.odit.services/lfk/backend/commit/8960aa5545ddeb57d4ef42c21c0ca6001dfeaea9)
- Implemented contact group setting on creation [`3b06d1a`](https://git.odit.services/lfk/backend/commit/3b06d1a6ef3c95eb5bb7d485accddabba0a8e4f7)
- 🧾New changelog file version [CI SKIP] [skip ci] [`32e054e`](https://git.odit.services/lfk/backend/commit/32e054eb84c869210fd483583ae5a6d0e2249cf9)
- Switched Address to embedded entity [`7fbe649`](https://git.odit.services/lfk/backend/commit/7fbe649dc90f4bb9f240c5a80fed447048e5e105)
@@ -39,11 +235,14 @@ All notable changes to this project will be documented in this file. Dates are d
- Fixed donor address check [`4824547`](https://git.odit.services/lfk/backend/commit/4824547dde4d7f90e9e2377a26df34cabf082fdb)
- Updated contact delete tests [`8ae53f1`](https://git.odit.services/lfk/backend/commit/8ae53f1c4930e2fd72eb230a5314336f3a45a611)
- Added address to contact response [`09e429f`](https://git.odit.services/lfk/backend/commit/09e429fc676c7dd370bba0495b072f81867bd250)
- Updated comments [`a4e8311`](https://git.odit.services/lfk/backend/commit/a4e8311cbd22588ecb4dc2fdbe05397b07d336f8)
- Updated the responseclasses to use the new address implementation [`dafac06`](https://git.odit.services/lfk/backend/commit/dafac06bc84d1b237096a561b3adcd3ca5cb1dd8)
- Added address validity check [`ae7c5ff`](https://git.odit.services/lfk/backend/commit/ae7c5ff0c387e9337d01a9dd819a4dddc208f6dd)
- 🧾New changelog file version [CI SKIP] [skip ci] [`da9a359`](https://git.odit.services/lfk/backend/commit/da9a3592510eacd2d67a127dc61e954343e0444b)
- 🚀Bumped version to v0.2.0 [`ddafd90`](https://git.odit.services/lfk/backend/commit/ddafd90d3e41fb9ee37172a8306c30d8483dfe2c)
- Merge pull request 'Implemented group contacts feature/104-contacts' (#108) from feature/104-contacts into dev [`a0c2b5a`](https://git.odit.services/lfk/backend/commit/a0c2b5ade8d198ec16d33b39e47205e8b03a669f)
- Updated comments [`a4e8311`](https://git.odit.services/lfk/backend/commit/a4e8311cbd22588ecb4dc2fdbe05397b07d336f8)
- Removed (now useless) relations [`673dea2`](https://git.odit.services/lfk/backend/commit/673dea2e5754e99ff77f7556d4fc03d4cca28a94)
- Added missing id property [`6b4b16c`](https://git.odit.services/lfk/backend/commit/6b4b16c13b0c2f55745ded3431cad2f4986be296)
- Added address validity check [`ae7c5ff`](https://git.odit.services/lfk/backend/commit/ae7c5ff0c387e9337d01a9dd819a4dddc208f6dd)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f53894b`](https://git.odit.services/lfk/backend/commit/f53894b16ac1c06ecbeeb0b63a56ac438b2fbe1b)
- Updated comments [`8bc01d3`](https://git.odit.services/lfk/backend/commit/8bc01d3f2406ce8e58c2ab2963c858495c510dcf)
- Fixed contact cascading [`179c2a5`](https://git.odit.services/lfk/backend/commit/179c2a5157fca036acf8d0e6a51821d377860bc1)

104
README.md
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,55 @@ 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 [ethereal.email](https://ethereal.email) as the mailserver).
| 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.
| 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
| MAIL_SERVER | String | N/A | The smtp server's ip-address/fqdn
| MAIL_PORT | String | N/A | The smtp server's port
| MAIL_USER | String | N/A | The username for sending mails
| MAIL_PASSWORD | String | N/A | The user's password for sending mails
| MAIL_FROM | String | N/A | The from-address for sending mails
| 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 +79,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`

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]
@@ -88,22 +117,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 +134,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 +353,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
@@ -340,6 +390,30 @@ SOFTWARE.
## License Text
# nodemailer
**Author**: Andris Reinman
**Repo**: [object Object]
**License**: MIT
**Description**: Easy as cake e-mail sending from your Node.js applications
## License Text
Copyright (c) 2011-2019 Andris Reinman
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 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.
# pg
**Author**: Brian Carlson <brian.m.carlson@gmail.com>
**Repo**: [object Object]
@@ -594,7 +668,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)
@@ -791,6 +865,35 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE
# @types/nodemailer
**Author**: undefined
**Repo**: [object Object]
**License**: MIT
**Description**: TypeScript definitions for Nodemailer
## License Text
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
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
# @types/uuid
**Author**: undefined
**Repo**: [object Object]
@@ -934,6 +1037,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

View File

@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-backend",
"version": "0.2.0",
"version": "0.4.3",
"main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend",
"author": {
@@ -35,14 +35,15 @@
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"libphonenumber-js": "^1.9.7",
"libphonenumber-js": "^1.9.9",
"mysql": "^2.18.1",
"nodemailer": "^6.4.17",
"pg": "^8.5.1",
"reflect-metadata": "^0.1.13",
"routing-controllers": "^0.9.0-alpha.6",
"routing-controllers-openapi": "^2.2.0",
"sqlite3": "5.0.0",
"typeorm": "^0.2.29",
"typeorm": "^0.2.30",
"typeorm-routing-controllers-extensions": "^0.2.0",
"typeorm-seeding": "^1.6.1",
"uuid": "^8.3.2",
@@ -52,10 +53,11 @@
"@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.20",
"@types/node": "^14.14.22",
"@types/nodemailer": "^6.4.0",
"@types/uuid": "^8.3.0",
"axios": "^0.21.1",
"cp-cli": "^2.0.0",
@@ -64,9 +66,9 @@
"release-it": "^14.2.2",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.11.7",
"ts-jest": "^26.4.4",
"ts-jest": "^26.5.0",
"ts-node": "^9.1.1",
"typedoc": "^0.20.14",
"typedoc": "^0.20.19",
"typescript": "^4.1.3"
},
"scripts": {
@@ -75,10 +77,12 @@
"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": "ts-node scripts/openapi_export.ts",
"licenses:export": "license-exporter --md",
"licenses:export": "license-exporter --markdown",
"release": "release-it --only-version"
},
"release-it": {

37
scripts/create_testenv.ts Normal file
View File

@@ -0,0 +1,37 @@
import consola from "consola";
import fs from "fs";
import nodemailer from "nodemailer";
nodemailer.createTestAccount((err, account) => {
if (err) {
console.error('Failed to create a testing account. ' + err.message);
return process.exit(1);
}
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=dev
POSTALCODE_COUNTRYCODE=DE
SEED_TEST_DATA=true
MAIL_SERVER=${account.smtp.host}
MAIL_PORT=${account.smtp.port}
MAIL_USER=${account.user}
MAIL_PASSWORD=${account.pass}
MAIL_FROM=${account.user}`
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");
}
});

View File

@@ -42,7 +42,7 @@ export function generateSpec(storage: MetadataArgsStorage, schemas) {
}
},
info: {
description: "The the backend API for the LfK! runner system.",
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
},

View File

@@ -9,7 +9,16 @@ export const config = {
jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
phone_validation_countrycode: getPhoneCodeLocale(),
postalcode_validation_countrycode: getPostalCodeLocale(),
version: process.env.VERSION || require('../package.json').version
version: process.env.VERSION || require('../package.json').version,
seedTestData: getDataSeeding(),
app_url: process.env.APP_URL || "http://localhost:8080",
mail_server: process.env.MAIL_SERVER,
mail_port: Number(process.env.MAIL_PORT) || 25,
mail_user: process.env.MAIL_USER,
mail_password: process.env.MAIL_PASSWORD,
mail_from: process.env.MAIL_FROM,
privacy_url: process.env.PRIVACY_URL || "/privacy",
imprint_url: process.env.IMPRINT_URL || "/imprint"
}
let errors = 0
if (typeof config.internal_port !== "number") {
@@ -30,4 +39,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

View File

@@ -1,103 +1,110 @@
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/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 { Logout } from '../models/responses/ResponseLogout';
@JsonController('/auth')
export class AuthController {
constructor() {
}
@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(ResponseAuth)
@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(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();
}
}
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 { 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 {
private mailer: Mailer;
constructor() {
this.mailer = new Mailer();
}
@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 })
@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) {
const reset_token: string = await passwordReset.toResetToken();
await this.mailer.sendResetMail(passwordReset.email, reset_token);
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();
}
}

View File

@@ -78,7 +78,7 @@ export class DonationController {
async postDistance(@Body({ validate: true }) createDonation: CreateDistanceDonation) {
let donation = await createDonation.toEntity();
donation = await this.distanceDonationRepository.save(donation);
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
}
@Put('/fixed/:id')
@@ -124,7 +124,7 @@ export class DonationController {
}
await this.distanceDonationRepository.save(await donation.update(oldDonation));
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
}
@Delete('/:id')

View File

@@ -28,7 +28,7 @@ export class GroupContactController {
@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'] });
const contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] });
contacts.forEach(contact => {
responseContacts.push(contact.toResponse());
});
@@ -42,7 +42,7 @@ export class GroupContactController {
@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'] })
let contact = await this.contactRepository.findOne({ id: id }, { relations: ['groups', 'groups.parentGroup'] })
if (!contact) { throw new GroupContactNotFoundError(); }
return contact.toResponse();
}
@@ -61,7 +61,7 @@ export class GroupContactController {
}
contact = await this.contactRepository.save(contact)
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse();
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
}
@Put('/:id')
@@ -83,7 +83,7 @@ export class GroupContactController {
}
await this.contactRepository.save(await contact.update(oldContact));
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups'] })).toResponse();
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
}
@Delete('/:id')
@@ -95,7 +95,7 @@ export class GroupContactController {
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'] });
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);

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 })

View File

@@ -0,0 +1,26 @@
import { Authorized, JsonController, Post } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { config } from '../config';
import { Mailer } from '../mailer';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
@JsonController('/mails')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class MailController {
private mailer: Mailer;
constructor() {
this.mailer = new Mailer();
}
@Post('/test')
@Authorized(["MAIL:CREATE"])
@ResponseSchema(ResponseEmpty, { statusCode: 200 })
@OpenAPI({ description: 'Sends a test email to the configured from-address.' })
async get() {
await this.mailer.sendTestMail(config.mail_from);
return new ResponseEmpty();
}
}

View File

@@ -8,6 +8,8 @@ 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';
@@ -49,6 +51,31 @@ export class RunnerController {
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)

View File

@@ -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/create/CreateRunnerOrganisation';
import { UpdateRunnerOrganisation } from '../models/actions/update/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: ['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: ['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.toEntity();
} catch (error) {
throw error;
}
runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation);
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['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.update(oldRunnerOrganisation));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['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> This won\'t delete the associated contact. <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: ['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;
}
}

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 { 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.scans', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.group', 'teams.runners.scans', 'teams.runners.scans.track'] })).allRunners; }
else { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', '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;
}
}

View File

@@ -0,0 +1,98 @@
import * as jwt from "jsonwebtoken";
import { Body, Get, JsonController, OnUndefined, Param, Post } 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 { RunnerEmailNeededError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
import { JwtCreator } from '../jwtcreator';
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 { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
@JsonController('/runners')
export class RunnerSelfServiceController {
private runnerRepository: Repository<Runner>;
private orgRepository: Repository<RunnerOrganization>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.runnerRepository = getConnectionManager().get().getRepository(Runner);
this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization);
}
@Get('/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)));
}
@Post('/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);
return response;
}
@Post('/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);
return response;
}
/**
* 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;
}
}

View File

@@ -6,6 +6,7 @@ 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.scans', 'runners.scans.track'] })).runners;
runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner));
});
return responseRunners;
}
@Post()
@Authorized("TEAM:CREATE")
@ResponseSchema(ResponseRunnerTeam)

View File

@@ -36,7 +36,7 @@ export class ScanController {
@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.scans.track', 'card', 'station'] });
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());
});
@@ -51,7 +51,7 @@ export class ScanController {
@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.scans.track', 'card', 'station'] })
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();
}
@@ -64,7 +64,7 @@ export class ScanController {
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.scans.track', 'card', 'station'] })).toResponse();
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Post("/trackscans")
@@ -75,7 +75,7 @@ export class ScanController {
async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan) {
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.scans.track', 'card', 'station'] })).toResponse();
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Put('/:id')
@@ -97,7 +97,7 @@ export class ScanController {
}
await this.scanRepository.save(await scan.update(oldScan));
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Put('/trackscans/:id')
@@ -120,7 +120,7 @@ export class ScanController {
}
await this.trackScanRepository.save(await scan.update(oldScan));
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Delete('/:id')
@@ -132,7 +132,7 @@ export class ScanController {
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.scans.track', 'card', 'station'] });
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();

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 => {

12
src/errors/MailErrors.ts Normal file
View File

@@ -0,0 +1,12 @@
import { IsString } from 'class-validator'
/**
* Error to throw when a permission couldn't be found.
*/
export class MailServerConfigError extends Error {
@IsString()
name = "MailServerConfigError"
@IsString()
message = "The SMTP server you provided couldn't be reached!"
}

View File

@@ -32,7 +32,18 @@ export class RunnerGroupNeededError extends NotAcceptableError {
name = "RunnerGroupNeededError"
@IsString()
message = "Runner's need to be part of one group (team or organisation)! \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."
}
/**

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 organisation's 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."
}

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."
}

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."
}

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.

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;
};

79
src/mailer.ts Normal file
View File

@@ -0,0 +1,79 @@
import fs from "fs";
import nodemailer from 'nodemailer';
import { MailOptions } from 'nodemailer/lib/json-transport';
import Mail from 'nodemailer/lib/mailer';
import { config } from './config';
import { MailServerConfigError } from './errors/MailErrors';
/**
* This class is responsible for all things mail sending.
* This uses the mail emplates from src/static/mail_templates
*/
export class Mailer {
private transport: Mail;
/**
* The class's default constructor.
* Creates the transporter and tests the connection.
*/
constructor() {
this.transport = nodemailer.createTransport({
host: config.mail_server,
port: config.mail_port,
auth: {
user: config.mail_user,
pass: config.mail_password
}
});
this.transport.verify(function (error, success) {
if (error) {
throw new MailServerConfigError();
}
});
}
/**
* Function for sending a test mail from the test mail template.
* @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 async sendResetMail(to_address: string, token: string) {
const reset_link = `${config.app_url}/reset/${(Buffer.from(token)).toString("base64")}`
const body_html = fs.readFileSync(__dirname + '/static/mail_templates/pw-reset.html', { encoding: 'utf8' }).replace("{{reset_link}}", reset_link).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
const body_txt = fs.readFileSync(__dirname + '/static/mail_templates/pw-reset.html', { encoding: 'utf8' }).replace("{{reset_link}}", reset_link).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
const mail: MailOptions = {
to: to_address,
subject: "LfK! Password Reset",
text: body_txt,
html: body_html
};
await this.sendMail(mail);
}
/**
* Function for sending a test mail from the test mail template.
* @param to_address The address the test mail will be sent to - this is the configured from-address by default.
*/
public async sendTestMail(to_address: string = config.mail_from) {
const body_html = fs.readFileSync(__dirname + '/static/mail_templates/test.html', { encoding: 'utf8' }).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
const body_txt = fs.readFileSync(__dirname + '/static/mail_templates/test.txt', { encoding: 'utf8' }).replace("{{recipient_mail}}", to_address).replace("{{copyright_owner}}", "LfK!").replace("{{link_imprint}}", `${config.app_url}/imprint`).replace("{{link_privacy}}", `${config.app_url}/privacy`);
const mail: MailOptions = {
to: to_address,
subject: "LfK! Test Mail",
text: body_txt,
html: body_html
};
await this.sendMail(mail);
}
/**
* Wrapper function for sending a mail via this object's transporter.
* @param mail MailOptions object containing the
*/
public async sendMail(mail: MailOptions) {
mail.from = config.mail_from;
await this.transport.sendMail(mail);
}
}

View File

@@ -42,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) {

View File

@@ -1,9 +1,9 @@
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 './create/CreateRunner';
@@ -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; }

View File

@@ -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 { 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;
}

View File

@@ -1,7 +1,7 @@
import { IsInt } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
import { RunnerOrganizationWrongTypeError } from '../../../errors/RunnerOrganizationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner';
@@ -50,6 +50,6 @@ export class CreateRunner extends CreateParticipant {
return group;
}
throw new RunnerOrganisationWrongTypeError;
throw new RunnerOrganizationWrongTypeError;
}
}

View File

@@ -1,30 +0,0 @@
import { IsObject, IsOptional } from 'class-validator';
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.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* Creates a new RunnerOrganisation entity from this.
*/
public async toEntity(): Promise<RunnerOrganisation> {
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
newRunnerOrganisation.name = this.name;
newRunnerOrganisation.contact = await this.getContact();
newRunnerOrganisation.address = this.address;
Address.validate(newRunnerOrganisation.address);
return newRunnerOrganisation;
}
}

View File

@@ -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;
}
}

View File

@@ -1,8 +1,8 @@
import { IsInt, IsNotEmpty } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError } from '../../../errors/RunnerOrganisationErrors';
import { RunnerOrganizationNotFoundError } from '../../../errors/RunnerOrganizationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { RunnerTeam } from '../../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
@@ -21,12 +21,12 @@ 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();
}
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganizationNotFoundError();; }
return parentGroup;
}

View File

@@ -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 });
}
}

View File

@@ -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;
}
}

View File

@@ -1,38 +0,0 @@
import { IsInt, IsObject, IsOptional } from 'class-validator';
import { Address } from '../../entities/Address';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from '../create/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.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* Updates a provided RunnerOrganisation entity based on this.
*/
public async update(organisation: RunnerOrganisation): Promise<RunnerOrganisation> {
organisation.name = this.name;
organisation.contact = await this.getContact();
if (!this.address) { organisation.address.reset(); }
else { organisation.address = this.address; }
Address.validate(organisation.address);
return organisation;
}
}

View File

@@ -0,0 +1,53 @@
import { IsBoolean, IsInt, IsObject, IsOptional } from 'class-validator';
import * as uuid from 'uuid';
import { Address } from '../../entities/Address';
import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
/**
* This class is used to update a RunnerOrganization entity (via put request).
*/
export class UpdateRunnerOrganization 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 organization's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* Is registration enabled for the updated organization?
*/
@IsOptional()
@IsBoolean()
registrationEnabled?: boolean = false;
/**
* Updates a provided RunnerOrganization entity based on this.
*/
public async update(organization: RunnerOrganization): Promise<RunnerOrganization> {
organization.name = this.name;
organization.contact = await this.getContact();
if (!this.address) { organization.address.reset(); }
else { organization.address = this.address; }
Address.validate(organization.address);
if (this.registrationEnabled && !organization.key) {
organization.key = uuid.v4().toUpperCase();
}
else {
organization.key = null;
}
return organization;
}
}

View File

@@ -1,8 +1,8 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError } from '../../../errors/RunnerOrganisationErrors';
import { RunnerOrganizationNotFoundError } from '../../../errors/RunnerOrganizationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { RunnerTeam } from '../../entities/RunnerTeam';
import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
@@ -28,12 +28,12 @@ export class UpdateRunnerTeam extends CreateRunnerGroup {
/**
* Loads the updated teams's parentGroup 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();
}
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganizationNotFoundError();; }
return parentGroup;
}

View File

@@ -0,0 +1,27 @@
import {
IsNotEmpty,
IsString
} from "class-validator";
import { Column, Entity, PrimaryColumn } from "typeorm";
/**
* Defines the ConfigFlag entity.
* This entity can be used to set some flags on db init.
*/
@Entity()
export class ConfigFlag {
/**
* The flag's name (primary).
*/
@PrimaryColumn()
@IsString()
option: string;
/**
* The flag's value.
*/
@Column()
@IsString()
@IsNotEmpty()
value: string;
}

View File

@@ -16,7 +16,7 @@ import { Scan } from "./Scan";
export class Runner extends Participant {
/**
* The runner's associated group.
* Can be a runner team or organisation.
* Can be a runner team or organization.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerGroup, group => group.runners)
@@ -65,7 +65,7 @@ export class Runner extends Participant {
*/
@IsInt()
public get distanceDonationAmount(): number {
return this.distanceDonations.reduce((sum, current) => sum + current.amountPerDistance, 0) * this.distance;
return this.distanceDonations.reduce((sum, current) => sum + current.amount, 0);
}
/**

View File

@@ -1,34 +1,44 @@
import { IsInt, IsOptional } from "class-validator";
import { IsInt, IsOptional, IsString } from "class-validator";
import { ChildEntity, Column, OneToMany } from "typeorm";
import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation';
import { ResponseRunnerOrganization } from '../responses/ResponseRunnerOrganization';
import { Address } from './Address';
import { Runner } from './Runner';
import { RunnerGroup } from "./RunnerGroup";
import { RunnerTeam } from "./RunnerTeam";
/**
* Defines the RunnerOrganisation entity.
* Defines the RunnerOrganization entity.
* This usually is a school, club or company.
*/
@ChildEntity()
export class RunnerOrganisation extends RunnerGroup {
export class RunnerOrganization extends RunnerGroup {
/**
* The organisations's address.
* The organizations's address.
*/
@IsOptional()
@Column(type => Address)
address?: Address;
/**
* The organisation's teams.
* Used to link teams to a organisation.
* The organization's teams.
* Used to link teams to a organization.
*/
@OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true })
teams: RunnerTeam[];
/**
* Returns all runners associated with this organisation (directly or indirectly via teams).
* The organization's api key for self-service registration.
* The api key can be used for the /runners/register/:token endpoint.
* Is has to be base64 encoded if used via the api (to keep url-safety).
*/
@Column({ nullable: true, unique: true })
@IsString()
@IsOptional()
key?: string;
/**
* Returns all runners associated with this organization (directly or indirectly via teams).
*/
public get allRunners(): Runner[] {
let returnRunners: Runner[] = new Array<Runner>();
@@ -58,7 +68,7 @@ export class RunnerOrganisation extends RunnerGroup {
/**
* Turns this entity into it's response class.
*/
public toResponse(): ResponseRunnerOrganisation {
return new ResponseRunnerOrganisation(this);
public toResponse(): ResponseRunnerOrganization {
return new ResponseRunnerOrganization(this);
}
}

View File

@@ -2,7 +2,7 @@ import { IsNotEmpty } from "class-validator";
import { ChildEntity, ManyToOne } from "typeorm";
import { ResponseRunnerTeam } from '../responses/ResponseRunnerTeam';
import { RunnerGroup } from "./RunnerGroup";
import { RunnerOrganisation } from "./RunnerOrganisation";
import { RunnerOrganization } from "./RunnerOrganization";
/**
* Defines the RunnerTeam entity.
@@ -13,11 +13,11 @@ export class RunnerTeam extends RunnerGroup {
/**
* The team's parent group.
* Every team has to be part of a runnerOrganisation - this get's checked on creation and update.
* Every team has to be part of a runnerOrganization - this get's checked on creation and update.
*/
@IsNotEmpty()
@ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })
parentGroup?: RunnerOrganisation;
@ManyToOne(() => RunnerOrganization, org => org.teams, { nullable: true })
parentGroup?: RunnerOrganization;
/**
* Turns this entity into it's response class.

View File

@@ -4,7 +4,6 @@ import {
IsString
} from "class-validator";
import { ChildEntity, Column } from "typeorm";
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { ResponseUserGroup } from '../responses/ResponseUserGroup';
import { Principal } from './Principal';
@@ -34,7 +33,7 @@ export class UserGroup extends Principal {
/**
* Turns this entity into it's response class.
*/
public toResponse(): ResponsePrincipal {
public toResponse(): ResponseUserGroup {
return new ResponseUserGroup(this);
}
}

View File

@@ -3,7 +3,7 @@
*/
export enum PermissionTarget {
RUNNER = 'RUNNER',
ORGANISATION = 'ORGANISATION',
ORGANIZATION = 'ORGANIZATION',
TEAM = 'TEAM',
TRACK = 'TRACK',
USER = 'USER',
@@ -15,5 +15,6 @@ export enum PermissionTarget {
STATION = 'STATION',
CARD = 'CARD',
DONATION = 'DONATION',
CONTACT = 'CONTACT'
CONTACT = 'CONTACT',
MAIL = 'MAIL'
}

View File

@@ -0,0 +1,34 @@
/**
* This enum contains all object types/entities a response can contain.
*/
export enum ResponseObjectType {
AUTH = 'AUTH',
DISTANCEDONATION = 'DISTANCEDONATION',
DONATION = 'DONATION',
DONOR = 'DONOR',
EMPTY = 'EMPTY',
GROUPCONTACT = 'GROUPCONTACT',
LOGOUT = 'LOGOUT',
PARTICIPANT = 'PARTICIPANT',
PERMISSION = 'PERMISSION',
PRINCIPAL = 'PRINCIPAL',
RUNNER = 'RUNNER',
RUNNERCARD = 'RUNNERCARD',
RUNNERGROUP = 'RUNNERGROUP',
RUNNERORGANIZATION = 'RUNNERORGANIZATION',
RUNNERTEAM = 'RUNNERTEAM',
SCAN = 'SCAN',
SCANSTATION = 'SCANSTATION',
SELFSERVICEDONATION = 'SELFSERVICEDONATION',
SELFSERVICERUNNER = 'SELFSERVICRUNNER',
STATS = 'STATS',
STATSCLIENT = 'STATSCLIENT',
STATSORGANIZATION = 'STATSORGANIZATION',
STATSRUNNER = 'STATSRUNNER',
STATSTEAM = 'STATSTEAM',
TRACK = 'TRACK',
TRACKSCAN = 'TRACKSCAN',
USER = 'USER',
USERGROUP = 'USERGROUP',
USERPERMISSIONS = 'USERPERMISSIONS',
}

View File

@@ -0,0 +1,13 @@
import { ResponseObjectType } from '../enums/ResponseObjectType';
/**
* Defines the repsonse interface.
* This forces all response classes to implement the interfaces properties.
*/
export interface IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType;
}

View File

@@ -1,9 +1,18 @@
import { IsInt, IsString } from 'class-validator';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the repsonse auth.
*/
export class ResponseAuth {
export class ResponseAuth implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.AUTH;
/**
* The access_token - JWT shortterm access token.
*/

View File

@@ -1,12 +1,19 @@
import { IsInt, IsObject, IsPositive } from 'class-validator';
import { DistanceDonation } from '../entities/DistanceDonation';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseDonation } from './ResponseDonation';
import { ResponseRunner } from './ResponseRunner';
/**
* Defines the distance donation response.
*/
export class ResponseDistanceDonation extends ResponseDonation {
export class ResponseDistanceDonation extends ResponseDonation implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.DISTANCEDONATION;
/**
* The donation's associated runner.
@@ -29,7 +36,7 @@ export class ResponseDistanceDonation extends ResponseDonation {
*/
public constructor(donation: DistanceDonation) {
super(donation);
this.runner = donation.runner.toResponse();
if (donation.runner) { this.runner = donation.runner.toResponse(); }
this.amountPerDistance = donation.amountPerDistance;
}
}

View File

@@ -1,11 +1,20 @@
import { IsInt, IsNotEmpty, IsPositive } from "class-validator";
import { Donation } from '../entities/Donation';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseDonor } from './ResponseDonor';
/**
* Defines the donation response.
*/
export class ResponseDonation {
export class ResponseDonation implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.DONATION;
/**
* The donation's id.
*/

View File

@@ -2,12 +2,19 @@ import {
IsBoolean, IsInt
} from "class-validator";
import { Donor } from '../entities/Donor';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseParticipant } from './ResponseParticipant';
/**
* Defines the donor response.
*/
export class ResponseDonor extends ResponseParticipant {
export class ResponseDonor extends ResponseParticipant implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.DONOR;
/**
* Does this donor need a receipt?

View File

@@ -1,9 +1,17 @@
import { IsString } from 'class-validator';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines a empty response object.
*/
export class ResponseEmpty {
export class ResponseEmpty implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.EMPTY;
@IsString()
response: string = "nothing here"
}

View File

@@ -1,12 +1,20 @@
import { IsInt, IsObject, IsString } from "class-validator";
import { Address } from '../entities/Address';
import { GroupContact } from '../entities/GroupContact';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/**
* Defines the group contact response.
*/
export class ResponseGroupContact {
export class ResponseGroupContact implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.GROUPCONTACT;
/**
* The contact's id.
*/
@@ -69,8 +77,10 @@ export class ResponseGroupContact {
this.email = contact.email;
this.address = contact.address;
this.groups = new Array<ResponseRunnerGroup>();
for (let group of contact.groups) {
this.groups.push(group.toResponse());
if (contact.groups) {
for (let group of contact.groups) {
this.groups.push(group.toResponse());
}
}
}
}

View File

@@ -1,9 +1,17 @@
import { IsString } from 'class-validator';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the logout response.
*/
export class Logout {
export class Logout implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.LOGOUT;
/**
* The logout's timestamp.
*/

View File

@@ -1,11 +1,19 @@
import { IsInt, IsObject, IsOptional, IsString } from "class-validator";
import { Address } from '../entities/Address';
import { Participant } from '../entities/Participant';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the participant response.
*/
export abstract class ResponseParticipant {
export abstract class ResponseParticipant implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.PARTICIPANT;
/**
* The participant's id.
*/

View File

@@ -7,12 +7,20 @@ import {
import { Permission } from '../entities/Permission';
import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponsePrincipal } from './ResponsePrincipal';
/**
* Defines the permission response.
*/
export class ResponsePermission {
export class ResponsePermission implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.PERMISSION;
/**
* The permission's id.
*/

View File

@@ -2,11 +2,19 @@ import {
IsInt
} from "class-validator";
import { Principal } from '../entities/Principal';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the principal response.
*/
export abstract class ResponsePrincipal {
export abstract class ResponsePrincipal implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.PRINCIPAL;
/**
* The principal's id.

View File

@@ -3,13 +3,20 @@ import {
IsObject
} from "class-validator";
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseParticipant } from './ResponseParticipant';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/**
* Defines the runner response.
*/
export class ResponseRunner extends ResponseParticipant {
export class ResponseRunner extends ResponseParticipant implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.RUNNER;
/**
* The runner's currently ran distance in meters.
@@ -21,7 +28,7 @@ export class ResponseRunner extends ResponseParticipant {
* The runner's group.
*/
@IsObject()
group: RunnerGroup;
group: ResponseRunnerGroup;
/**
* Creates a ResponseRunner object from a runner.
@@ -31,6 +38,6 @@ export class ResponseRunner extends ResponseParticipant {
super(runner);
if (!runner.scans) { this.distance = 0 }
else { this.distance = runner.validScans.reduce((sum, current) => sum + current.distance, 0); }
this.group = runner.group;
if (runner.group) { this.group = runner.group.toResponse(); }
}
}

View File

@@ -1,11 +1,19 @@
import { IsBoolean, IsEAN, IsInt, IsNotEmpty, IsObject, IsString } from "class-validator";
import { RunnerCard } from '../entities/RunnerCard';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunner } from './ResponseRunner';
/**
* Defines the runner card response.
*/
export class ResponseRunnerCard {
export class ResponseRunnerCard implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.RUNNERCARD;
/**
* The card's id.
*/

View File

@@ -1,11 +1,19 @@
import { IsInt, IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator";
import { GroupContact } from '../entities/GroupContact';
import { RunnerGroup } from '../entities/RunnerGroup';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseGroupContact } from './ResponseGroupContact';
/**
* Defines the runnerGroup response.
*/
export abstract class ResponseRunnerGroup {
export abstract class ResponseRunnerGroup implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.RUNNERGROUP;
/**
* The runnerGroup's id.
*/
@@ -26,7 +34,7 @@ export abstract class ResponseRunnerGroup {
*/
@IsObject()
@IsOptional()
contact?: GroupContact;
contact?: ResponseGroupContact;
/**
* Creates a ResponseRunnerGroup object from a runnerGroup.
@@ -35,6 +43,6 @@ export abstract class ResponseRunnerGroup {
public constructor(group: RunnerGroup) {
this.id = group.id;
this.name = group.name;
this.contact = group.contact;
if (group.contact) { this.contact = group.contact.toResponse(); };
}
}

View File

@@ -1,39 +0,0 @@
import {
IsArray,
IsObject,
IsOptional
} from "class-validator";
import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/**
* Defines the runnerOrganisation response.
*/
export class ResponseRunnerOrganisation extends ResponseRunnerGroup {
/**
* The runnerOrganisation's address.
*/
@IsObject()
@IsOptional()
address?: Address;
/**
* The runnerOrganisation associated teams.
*/
@IsArray()
teams: RunnerTeam[];
/**
* Creates a ResponseRunnerOrganisation object from a runnerOrganisation.
* @param org The runnerOrganisation the response shall be build for.
*/
public constructor(org: RunnerOrganisation) {
super(org);
this.address = org.address;
this.teams = org.teams;
}
}

View File

@@ -0,0 +1,75 @@
import {
IsArray,
IsBase64,
IsBoolean,
IsObject,
IsOptional,
IsString
} from "class-validator";
import { Address } from '../entities/Address';
import { RunnerOrganization } from '../entities/RunnerOrganization';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
import { ResponseRunnerTeam } from './ResponseRunnerTeam';
/**
* Defines the runnerOrganization response.
*/
export class ResponseRunnerOrganization extends ResponseRunnerGroup implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.RUNNERORGANIZATION;
/**
* The runnerOrganization's address.
*/
@IsObject()
@IsOptional()
address?: Address;
/**
* The runnerOrganization associated teams.
*/
@IsArray()
teams: ResponseRunnerTeam[];
/**
* The organization's registration key.
* If registration is disabled this is null.
*/
@IsString()
@IsOptional()
@IsBase64()
registrationKey?: string;
/**
* Is registration enabled for the organization?
*/
@IsOptional()
@IsBoolean()
registrationEnabled?: boolean = true;
/**
* Creates a ResponseRunnerOrganization object from a runnerOrganization.
* @param org The runnerOrganization the response shall be build for.
*/
public constructor(org: RunnerOrganization) {
super(org);
this.address = org.address;
this.teams = new Array<ResponseRunnerTeam>();
if (org.teams) {
for (let team of org.teams) {
this.teams.push(team.toResponse());
}
}
if (!org.key) { this.registrationEnabled = false; }
else { this.registrationKey = Buffer.from(org.key).toString('base64'); }
}
}

View File

@@ -1,19 +1,26 @@
import { IsNotEmpty, IsObject } from "class-validator";
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
import { ResponseRunnerOrganization } from './ResponseRunnerOrganization';
/**
* Defines the runnerTeam response.
*/
export class ResponseRunnerTeam extends ResponseRunnerGroup {
export class ResponseRunnerTeam extends ResponseRunnerGroup implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.RUNNERTEAM;
/**
* The runnerTeam's parent group (organisation).
* The runnerTeam's parent group (organization).
*/
@IsObject()
@IsNotEmpty()
parentGroup: RunnerOrganisation;
parentGroup: ResponseRunnerOrganization;
/**
* Creates a ResponseRunnerTeam object from a runnerTeam.
@@ -21,6 +28,6 @@ export class ResponseRunnerTeam extends ResponseRunnerGroup {
*/
public constructor(team: RunnerTeam) {
super(team);
this.parentGroup = team.parentGroup;
if (team.parentGroup) { this.parentGroup = team.parentGroup.toResponse(); }
}
}

View File

@@ -1,11 +1,19 @@
import { IsBoolean, IsInt, IsNotEmpty, IsPositive } from "class-validator";
import { Scan } from '../entities/Scan';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunner } from './ResponseRunner';
/**
* Defines the scan response.
*/
export class ResponseScan {
export class ResponseScan implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.SCAN;
/**
* The scans's id.
*/
@@ -39,7 +47,7 @@ export class ResponseScan {
*/
public constructor(scan: Scan) {
this.id = scan.id;
this.runner = scan.runner.toResponse();
if (scan.runner) { this.runner = scan.runner.toResponse(); }
this.distance = scan.distance;
this.valid = scan.valid;
}

View File

@@ -11,12 +11,20 @@ import {
IsString
} from "class-validator";
import { ScanStation } from '../entities/ScanStation';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseTrack } from './ResponseTrack';
/**
* Defines the statsClient response.
*/
export class ResponseScanStation {
export class ResponseScanStation implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.SCANSTATION;
/**
* The client's id.
*/
@@ -64,7 +72,7 @@ export class ResponseScanStation {
this.description = station.description;
this.prefix = station.prefix;
this.key = "Only visible on creation.";
this.track = station.track;
if (station.track) { this.track = station.track.toResponse(); }
this.enabled = station.enabled;
}
}

View File

@@ -0,0 +1,44 @@
import { IsInt, IsNotEmpty, IsPositive } from 'class-validator';
import { DistanceDonation } from '../entities/DistanceDonation';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the runner selfservice donation response.
* Why? B/C runner's are not allowed to view all information available to admin users.
*/
export class ResponseSelfServiceDonation implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.SELFSERVICEDONATION;
/**
* The donation's donor.
*/
@IsNotEmpty()
donor: string;
/**
* The donation's amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
amount: number;
/**
* The donation's amount donated per distance.
* The amount the donor set to be donated per kilometer that the runner ran.
*/
@IsInt()
@IsPositive()
amountPerDistance: number;
public constructor(donation: DistanceDonation) {
if (!donation.donor.middlename) { this.donor = donation.donor.firstname + " " + donation.donor.lastname; }
else { this.donor = donation.donor.firstname + " " + donation.donor.middlename + " " + donation.donor.lastname; }
this.amountPerDistance = donation.amountPerDistance;
this.amount = donation.amount;
}
}

View File

@@ -0,0 +1,90 @@
import { IsInt, IsOptional, IsString } from "class-validator";
import { DistanceDonation } from '../entities/DistanceDonation';
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerTeam } from '../entities/RunnerTeam';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseParticipant } from './ResponseParticipant';
import { ResponseSelfServiceDonation } from './ResponseSelfServiceDonation';
/**
* Defines the runner selfservice response.
* Why? B/C runner's are not allowed to view all information available to admin users.
*/
export class ResponseSelfServiceRunner extends ResponseParticipant implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.SELFSERVICERUNNER;
/**
* The runner's currently ran distance in meters.
*/
@IsInt()
distance: number;
/**
* The runner's currently collected donations.
*/
@IsInt()
donationAmount: number;
/**
* The runner's group as a string (mix of org and team).
*/
@IsString()
group: string;
/**
* The runner's associated donations.
*/
@IsString()
donations: ResponseSelfServiceDonation[]
/**
* The runner's self-service jwt for auth.
* Will only get delivered on registration/via email.
*/
@IsString()
@IsOptional()
token: string;
/**
* Creates a ResponseRunner object from a runner.
* @param runner The user the response shall be build for.
*/
public constructor(runner: Runner) {
super(runner);
this.distance = runner.distance;
this.donationAmount = runner.distanceDonationAmount;
this.group = this.getTeamString(runner.group);
this.donations = this.getDonations(runner.distanceDonations);
}
/**
* Parses a runner's group into a string.
* If the runner's group is a team: `org name/team name`
* If the runner's group is an org: `org name`
* @param group The group that shall get parsed to a string.
*/
private getTeamString(group: RunnerGroup): string {
if (group instanceof RunnerTeam) {
return group.parentGroup.name + "/" + group.name;
}
return group.name;
}
/**
* Converts all of the runner's donations to ResponseSelfServiceDonations.
* @param donations The donations that shall be converted to ResponseSelfServiceDonations.
*/
private getDonations(donations: DistanceDonation[]): ResponseSelfServiceDonation[] {
let responseDonations = new Array<ResponseSelfServiceDonation>();
for (let donation of donations) {
responseDonations.push(new ResponseSelfServiceDonation(donation));
}
return responseDonations;
}
}

View File

@@ -3,16 +3,24 @@ import {
} from "class-validator";
import { Donation } from '../entities/Donation';
import { Runner } from '../entities/Runner';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerOrganization } from '../entities/RunnerOrganization';
import { RunnerTeam } from '../entities/RunnerTeam';
import { Scan } from '../entities/Scan';
import { User } from '../entities/User';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the stats response.
* The stats response calculates some basic stats for a dashboard or public display.
*/
export class ResponseStats {
export class ResponseStats implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.STATS;
/**
* The amount of runners registered in the system.
*/
@@ -26,7 +34,7 @@ export class ResponseStats {
total_teams: number;
/**
* The amount of organisations registered in the system.
* The amount of organizations registered in the system.
*/
@IsInt()
total_orgs: number;
@@ -70,7 +78,7 @@ export class ResponseStats {
* @param scans Array containing all scans - no relations have to be resolved.
* @param donations Array containing all donations - the following relations have to be resolved: runner, runner.scans, runner.scans.track
*/
public constructor(runners: Runner[], teams: RunnerTeam[], orgs: RunnerOrganisation[], users: User[], scans: Scan[], donations: Donation[]) {
public constructor(runners: Runner[], teams: RunnerTeam[], orgs: RunnerOrganization[], users: User[], scans: Scan[], donations: Donation[]) {
this.total_runners = runners.length;
this.total_teams = teams.length;
this.total_orgs = orgs.length;

View File

@@ -8,11 +8,19 @@ import {
IsString
} from "class-validator";
import { StatsClient } from '../entities/StatsClient';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the statsClient response.
*/
export class ResponseStatsClient {
export class ResponseStatsClient implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.STATSCLIENT;
/**
* The client's id.
*/

View File

@@ -3,13 +3,21 @@ import {
IsString
} from "class-validator";
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerOrganization } from '../entities/RunnerOrganization';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the org stats response.
* This differs from the normal org responce.
*/
export class ResponseStatsOrgnisation {
export class ResponseStatsOrgnisation implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.STATSORGANIZATION;
/**
* The orgs's id.
*/
@@ -35,10 +43,10 @@ export class ResponseStatsOrgnisation {
donationAmount: number;
/**
* Creates a new organisation stats response from a organisation
* @param org The organisation whoes response shall be generated - the following relations have to be resolved: runners, runners.scans, runners.distanceDonations, runners.scans.track, teams, teams.runners, teams.runners.scans, teams.runners.distanceDonations, teams.runners.scans.track
* Creates a new organization stats response from a organization
* @param org The organization whoes response shall be generated - the following relations have to be resolved: runners, runners.scans, runners.distanceDonations, runners.scans.track, teams, teams.runners, teams.runners.scans, teams.runners.distanceDonations, teams.runners.scans.track
*/
public constructor(org: RunnerOrganisation) {
public constructor(org: RunnerOrganization) {
this.name = org.name;
this.id = org.id;
this.distance = org.distance;

View File

@@ -4,13 +4,21 @@ import {
IsString
} from "class-validator";
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/**
* Defines the runner stats response.
* This differs from the normal runner responce.
*/
export class ResponseStatsRunner {
export class ResponseStatsRunner implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.STATSRUNNER;
/**
* The runner's id.
*/
@@ -51,7 +59,7 @@ export class ResponseStatsRunner {
* The runner's group.
*/
@IsObject()
group: RunnerGroup;
group: ResponseRunnerGroup;
/**
* Creates a new runner stats response from a runner
@@ -64,6 +72,6 @@ export class ResponseStatsRunner {
this.lastname = runner.lastname;
this.distance = runner.distance;
this.donationAmount = runner.distanceDonationAmount;
this.group = runner.group;
this.group = runner.group.toResponse();
}
}

View File

@@ -3,14 +3,22 @@ import {
IsObject,
IsString
} from "class-validator";
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerTeam } from '../entities/RunnerTeam';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
/**
* Defines the team stats response.
* This differs from the normal team responce.
*/
export class ResponseStatsTeam {
export class ResponseStatsTeam implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.STATSTEAM;
/**
* The team's id.
*/
@@ -39,7 +47,7 @@ export class ResponseStatsTeam {
* The teams's parent group.
*/
@IsObject()
parent: RunnerGroup;
parent: ResponseRunnerGroup;
/**
* Creates a new team stats response from a team
@@ -48,7 +56,7 @@ export class ResponseStatsTeam {
public constructor(team: RunnerTeam) {
this.name = team.name;
this.id = team.id;
this.parent = team.parentGroup;
this.parent = team.parentGroup.toResponse();
this.distance = team.distance;
this.donationAmount = team.distanceDonationAmount;
}

View File

@@ -1,11 +1,19 @@
import { IsInt, IsOptional, IsString } from "class-validator";
import { TrackLapTimeCantBeNegativeError } from '../../errors/TrackErrors';
import { Track } from '../entities/Track';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the track response.
*/
export class ResponseTrack {
export class ResponseTrack implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.TRACK;
/**
* The track's id.
*/

View File

@@ -1,5 +1,7 @@
import { IsDateString, IsNotEmpty } from "class-validator";
import { TrackScan } from '../entities/TrackScan';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponseRunnerCard } from './ResponseRunnerCard';
import { ResponseScan } from './ResponseScan';
import { ResponseScanStation } from './ResponseScanStation';
@@ -8,10 +10,16 @@ import { ResponseTrack } from './ResponseTrack';
/**
* Defines the trackScan response.
*/
export class ResponseTrackScan extends ResponseScan {
export class ResponseTrackScan extends ResponseScan implements IResponse {
/**
* The scan's associated track.
*/
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.TRACKSCAN;
/**
* The scan's associated track.
*/
@IsNotEmpty()
track: ResponseTrack;
@@ -41,8 +49,8 @@ export class ResponseTrackScan extends ResponseScan {
public constructor(scan: TrackScan) {
super(scan);
this.track = new ResponseTrack(scan.track);
this.card = scan.card.toResponse();
this.station = scan.station.toResponse();
if (scan.card) { scan.card.toResponse(); }
if (scan.station) { scan.station.toResponse(); }
this.timestamp = scan.timestamp;
this.distance = scan.distance;
}

View File

@@ -6,13 +6,21 @@ import {
IsString
} from "class-validator";
import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponsePrincipal } from './ResponsePrincipal';
import { ResponseUserGroup } from './ResponseUserGroup';
/**
* Defines the user response.
*/
export class ResponseUser extends ResponsePrincipal {
export class ResponseUser extends ResponsePrincipal implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.USER;
/**
* The user's first name.
*/
@@ -66,7 +74,7 @@ export class ResponseUser extends ResponsePrincipal {
*/
@IsArray()
@IsOptional()
groups: UserGroup[];
groups: ResponseUserGroup[];
/**
* The user's permissions.
@@ -90,10 +98,13 @@ export class ResponseUser extends ResponsePrincipal {
this.username = user.username;
this.enabled = user.enabled;
this.profilePic = user.profilePic;
this.groups = user.groups;
this.groups = new Array<ResponseUserGroup>();
this.permissions = user.allPermissions;
if (this.groups) {
this.groups.forEach(function (g) { delete g.permissions });
if (user.groups) {
for (let group of user.groups) {
delete group.permissions;
this.groups.push(group.toResponse());
}
}
}
}

View File

@@ -1,12 +1,20 @@
import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { Permission } from '../entities/Permission';
import { UserGroup } from '../entities/UserGroup';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponsePermission } from './ResponsePermission';
import { ResponsePrincipal } from './ResponsePrincipal';
/**
* Defines the userGroup response.
*/
export class ResponseUserGroup extends ResponsePrincipal {
export class ResponseUserGroup extends ResponsePrincipal implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.USERGROUP;
/**
* The userGroup's name.
*/
@@ -26,7 +34,7 @@ export class ResponseUserGroup extends ResponsePrincipal {
*/
@IsArray()
@IsOptional()
permissions: Permission[];
permissions: ResponsePermission[];
/**
* Creates a ResponseUserGroup object from a userGroup.
@@ -36,6 +44,10 @@ export class ResponseUserGroup extends ResponsePrincipal {
super(group);
this.name = group.name;
this.description = group.description;
this.permissions = group.permissions;
if (group.permissions) {
for (let permission of group.permissions) {
this.permissions.push(permission.toResponse());
}
}
}
}

View File

@@ -5,12 +5,20 @@ import {
IsOptional
} from "class-validator";
import { User } from '../entities/User';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
import { ResponsePermission } from './ResponsePermission';
/**
* Defines the user permission response (get /api/users/:id/permissions).
*/
export class ResponseUserPermissions {
export class ResponseUserPermissions implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.USERPERMISSIONS;
/**
* The permissions directly granted to the user.
*/

View File

@@ -0,0 +1,15 @@
import { Connection } from 'typeorm';
import { Factory, Seeder } from 'typeorm-seeding';
import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization';
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
/**
* Seeds the public runner org (named: "Citizen" by default).
*/
export default class SeedPublicOrg implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> {
let publicOrg = new CreateRunnerOrganization();
publicOrg.name = "Citizen";
await connection.getRepository(RunnerOrganization).save(await publicOrg.toEntity());
}
}

View File

@@ -0,0 +1,93 @@
import { Connection } from 'typeorm';
import { Factory, Seeder } from 'typeorm-seeding';
import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
import { CreateRunner } from '../models/actions/create/CreateRunner';
import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization';
import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
import { Address } from '../models/entities/Address';
import { GroupContact } from '../models/entities/GroupContact';
import { Runner } from '../models/entities/Runner';
import { RunnerGroup } from '../models/entities/RunnerGroup';
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
import { RunnerTeam } from '../models/entities/RunnerTeam';
/**
* Seeds a test runner org with a test runner team ans some test runners.
* Usefull for testing or demo instances.
*/
export default class SeedTestRunners implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> {
let testOrg: RunnerOrganization = await this.createTestOrg(connection);
let testTeam: RunnerTeam = await this.createTestTeam(connection, testOrg);
await this.createTestContact(connection, testOrg);
await this.createTestRunners(connection, testOrg);
await this.createTestRunners(connection, testTeam);
}
public async createTestOrg(connection: Connection): Promise<RunnerOrganization> {
let testOrg = new CreateRunnerOrganization();
testOrg.name = "Test Org";
testOrg.address = new Address();
testOrg.address.address1 = "Test street 1";
testOrg.address.city = "Herzogenaurach";
testOrg.address.country = "Germany";
testOrg.address.postalcode = "90174";
return await connection.getRepository(RunnerOrganization).save(await testOrg.toEntity());
}
public async createTestTeam(connection: Connection, org: RunnerOrganization): Promise<RunnerTeam> {
let testTeam = new CreateRunnerTeam();
testTeam.name = "Test Team";
testTeam.parentGroup = org.id;
return await connection.getRepository(RunnerTeam).save(await testTeam.toEntity());
}
public async createTestRunners(connection: Connection, group: RunnerGroup) {
for (let first of this.firstnames) {
for (let last of this.lastnames) {
let runner = new CreateRunner;
runner.firstname = first;
runner.lastname = last;
runner.middlename = group.name;
runner.group = group.id;
await connection.getRepository(Runner).save(await runner.toEntity());
}
}
}
public async createTestContact(connection: Connection, group: RunnerGroup) {
let contact = new CreateGroupContact;
contact.firstname = "Test";
contact.lastname = "Contact";
contact.email = "test.contact@dev.lauf-fuer-kaya.de";
contact.groups = group.id;
contact.address = new Address();
contact.address.address1 = "First Contact Street 100";
contact.address.city = "Herzogenaurach";
contact.address.country = "Germany";
contact.address.postalcode = "90174";
await connection.getRepository(GroupContact).save(await contact.toEntity());
}
private firstnames = [
"Peter",
"Matze",
"Tine",
"Uta",
"Fabian",
"Unicode:ÖÄ?✔⚠"
]
private lastnames = [
"Muster",
"Example",
"Müller",
"Unicode:搆Ǩ>ÙՠƳ|"
]
}

View File

View File

@@ -0,0 +1,384 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>LfK! - Passwort zurücksetzen</title> <!-- The title tag shows in email notifications, like Android 4.4. -->
<meta charset="utf-8"> <!-- utf-8 works for most cases -->
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
<meta name="viewport" content="width=device-width"> <!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting"> <!-- Disable auto-scale in iOS 10 Mail entirely -->
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no"> <!-- Tell iOS not to automatically link certain text strings. -->
<!-- CSS Reset : BEGIN -->
<style>
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin:0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. */
table {
border: 0;
border-spacing: 0;
border-collapse: collapse
}
/* What it does: Forces Samsung Android mail clients to use the entire viewport. */
#MessageViewBody,
#MessageWebViewDiv{
width: 100% !important;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode:bicubic;
}
/* What it does: Prevents Windows 10 Mail from underlining links despite inline CSS. Styles for underlined links should be inline. */
a {
text-decoration: none;
}
/* What it does: A work-around for email clients automatically linking certain text strings. */
/* iOS */
a[x-apple-data-detectors],
.unstyle-auto-detected-links a,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
u + #body a, /* Gmail */
#MessageViewBody a /* Samsung Mail */
{
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
/* What it does: Prevents Gmail from changing the text color in conversation threads. */
.im {
color: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display:none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
u ~ div .email-container {
min-width: 320px !important;
}
}
/* iPhone 6, 6S, 7, 8, and X */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
u ~ div .email-container {
min-width: 375px !important;
}
}
/* iPhone 6+, 7+, and 8+ */
@media only screen and (min-device-width: 414px) {
u ~ div .email-container {
min-width: 414px !important;
}
}
</style>
<!-- What it does: Helps DPI scaling in Outlook 2007-2013 -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!-- CSS Reset : END -->
<!-- Progressive Enhancements : BEGIN -->
<style>
/* What it does: Hover styles for buttons and tags */
.s-btn__primary:hover {
background: #0077CC !important;
border-color: #0077CC !important;
}
.s-btn__white:hover {
background: #EFF0F1 !important;
border-color: #EFF0F1 !important;
}
.s-btn__outlined:hover {
background: rgba(0,119,204,.05) !important;
color: #005999 !important;
}
.s-tag:hover,
.post-tag:hover {
border-color: #cee0ed !important;
background: #cee0ed !important;
}
/* What it does: Styles markdown links that we can't write inline CSS for. */
.has-markdown a,
.has-markdown a:visited {
color: #0077CC !important;
text-decoration: none !important;
}
/* What it does: Styles markdown code blocks that we can't write inline CSS for. */
code {
padding: 1px 5px;
background-color: #EFF0F1;
color: #242729;
font-size: 13px;
line-height: inherit;
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
}
pre {
margin: 0 0 15px;
line-height: 17px;
background-color: #EFF0F1;
padding: 4px 8px;
border-radius: 3px;
overflow-x: auto;
}
pre code {
margin: 0 0 15px;
padding: 0;
line-height: 17px;
background-color: none;
}
/* What it does: Styles markdown blockquotes that we can't write inline CSS for. */
blockquote {
margin: 0 0 15px;
padding: 4px 10px;
background-color: #FFF8DC;
border-left: 2px solid #ffeb8e;
}
blockquote p {
padding: 4px 0;
margin: 0;
overflow-wrap: break-word;
}
/* What it does: Rounds corners in email clients that support it */
.bar {
border-radius: 5px;
}
.btr {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.bbr {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
@media screen and (max-width: 680px) {
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* Hides things in small viewports. */
.hide-on-mobile {
display: none !important;
max-height: 0 !important;
overflow: hidden !important;
visibility: hidden !important;
}
/* What it does: Utility classes to reduce spacing for smaller viewports. */
.sm-p-none {padding: 0 !important;}
.sm-pt-none {padding-top: 0 !important;}
.sm-pb-none {padding-bottom: 0 !important;}
.sm-pr-none {padding-right: 0 !important;}
.sm-pl-none {padding-left: 0 !important;}
.sm-px-none {padding-left: 0 !important; padding-right: 0 !important;}
.sm-py-none {padding-top: 0 !important; padding-bottom: 0 !important;}
.sm-p {padding: 20px !important;}
.sm-pt {padding-top: 20px !important;}
.sm-pb {padding-bottom: 20px !important;}
.sm-pr {padding-right: 20px !important;}
.sm-pl {padding-left: 20px !important;}
.sm-px {padding-left: 20px !important; padding-right: 20px !important;}
.sm-py {padding-top: 20px !important; padding-bottom: 20px !important;}
.sm-mb {margin-bottom: 20px !important;}
/* What it does: Utility classes to kill border radius for smaller viewports. Used mainly on the email's main container(s). */
.bar,
.btr,
.bbr {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
</style>
<!-- Progressive Enhancements : END -->
</head>
<!--
The email background color is defined in three places, just below. If you change one, remember to change the others.
1. body tag: for most email clients
2. center tag: for Gmail and Inbox mobile apps and web versions of Gmail, GSuite, Inbox, Yahoo, AOL, Libero, Comcast, freenet, Mail.ru, Orange.fr
3. mso conditional: For Windows 10 Mail
-->
<body width="100%" style="margin: 0; padding: 0 !important; background: #f3f3f5; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #f3f3f5;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f3f5;">
<tr>
<td>
<![endif]-->
<!-- Visually Hidden Preview Text : BEGIN -->
<div style="display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;">
LfK! - Password reset
</div>
<!-- Visually Hidden Preview Text : END -->
<div class="email-container" style="max-width: 680px; margin: 0 auto;">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="max-width: 680px; width:100%">
<tr>
<td style="padding: 30px; background-color: #ffffff;" class="sm-p bar">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tr>
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
<h1 style="font-weight: bold; font-size: 27px; line-height: 27px; color: #0C0D0E; margin: 0 0 15px 0;">LfK!</h1>
</td>
</tr>
<tr>
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
<h1 style="font-weight: bold; font-size: 21px; line-height: 21px; color: #0C0D0E; margin: 0 0 15px 0;">Password reset</h1>
<p style="margin: 0 0 15px;" class="has-markdown">A password reset for your account got requested.<br><b>If you didn't request the reset please ignore this mail.</b><br>Your password won't be changed until you click the reset link below and set a new one.</p>
</td>
</tr>
<!-- Button Row : BEGIN -->
<tr>
<td>
<!-- Button : BEGIN -->
<table align="left" border="0" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="s-btn s-btn__primary" style="border-radius: 4px; background: #0095ff;">
<a class="s-btn s-btn__primary" href="{{reset_link}}" target="_parent" style="background: #0095FF; border: 1px solid #0077cc; box-shadow: inset 0 1px 0 0 rgba(102,191,255,.75); font-family: arial, sans-serif; font-size: 17px; line-height: 17px; color: #ffffff; text-align: center; text-decoration: none; padding: 13px 17px; display: block; border-radius: 4px; white-space: nowrap;">Reset password</a>
</td>
</tr>
</table>
<!-- Button : END -->
</td>
</tr>
<!-- Button Row : END -->
</table>
</td>
</tr>
<!-----------------------------
EMAIL BODY : END
------------------------------>
<!-- Footer : BEGIN -->
<tr>
<td style="padding: 30px;" class="sm-p">
<table align="left" border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<!-- Subscription Info : BEGIN -->
<tr>
<td style="padding-bottom: 10px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
Copyright © {{copyright_owner}}. All rights reserved.
</td>
</tr>
<tr>
<td style="font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
<a href="{{link_imprint}}"
style="color: #9199A1; text-decoration: underline;">Imprint</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{{link_privacy}}" style="color: #9199A1; text-decoration: underline;">Privacy</a>
</td>
</tr>
<!-- Subscription Info : BEGIN -->
<!-- HR line : BEGIN -->
<tr>
<td style="padding: 30px 0;" width="100%" class="sm-py">
<table aria-hidden="true" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%">
<tr>
<td height="1" width="100%" style="font-size: 0; line-height: 0; border-top: 1px solid #D6D8DB;">&nbsp;</td>
</tr>
</table>
</td>
</tr>
<!-- HR line : END -->
<tr>
<td style="padding-bottom: 5px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">This mail was sent to <strong>{{recipient_mail}}</strong> because someone request a password reset for a account linked to the mail address.</td>
</tr>
<!-- Sender Info : END -->
</table>
</td>
</tr>
<!-- Footer : END -->
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</center>
</body>
</html>

View File

@@ -0,0 +1,12 @@
LfK! - Password reset.
A password reset for your account got requested
If you didn't request the reset please ignore this mail
Your password won't be changed until you click the reset link below and set a new one.
Reset: {{reset_link}}
Copyright © {{copyright_owner}}. All rights reserved.
Imprint: {{link_imprint}} | Privacy: {{link_privacy}}
This mail was sent to {{recipient_mail}} because someone request a password reset for a account linked to the mail address.

View File

@@ -0,0 +1,369 @@
<!DOCTYPE html>
<html lang="de" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<title>LfK! - Mail test</title> <!-- The title tag shows in email notifications, like Android 4.4. -->
<meta charset="utf-8"> <!-- utf-8 works for most cases -->
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
<meta name="viewport" content="width=device-width"> <!-- Forcing initial-scale shouldn't be necessary -->
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- Use the latest (edge) version of IE rendering engine -->
<meta name="x-apple-disable-message-reformatting"> <!-- Disable auto-scale in iOS 10 Mail entirely -->
<meta name="format-detection" content="telephone=no,address=no,email=no,date=no,url=no"> <!-- Tell iOS not to automatically link certain text strings. -->
<!-- CSS Reset : BEGIN -->
<style>
/* What it does: Remove spaces around the email design added by some email clients. */
/* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */
html,
body {
margin: 0 auto !important;
padding: 0 !important;
height: 100% !important;
width: 100% !important;
}
/* What it does: Stops email clients resizing small text. */
* {
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
/* What it does: Centers email on Android 4.4 */
div[style*="margin: 16px 0"] {
margin:0 !important;
}
/* What it does: Stops Outlook from adding extra spacing to tables. */
table,
td {
mso-table-lspace: 0pt !important;
mso-table-rspace: 0pt !important;
}
/* What it does: Fixes webkit padding issue. */
table {
border: 0;
border-spacing: 0;
border-collapse: collapse
}
/* What it does: Forces Samsung Android mail clients to use the entire viewport. */
#MessageViewBody,
#MessageWebViewDiv{
width: 100% !important;
}
/* What it does: Uses a better rendering method when resizing images in IE. */
img {
-ms-interpolation-mode:bicubic;
}
/* What it does: Prevents Windows 10 Mail from underlining links despite inline CSS. Styles for underlined links should be inline. */
a {
text-decoration: none;
}
/* What it does: A work-around for email clients automatically linking certain text strings. */
/* iOS */
a[x-apple-data-detectors],
.unstyle-auto-detected-links a,
.aBn {
border-bottom: 0 !important;
cursor: default !important;
color: inherit !important;
text-decoration: none !important;
font-size: inherit !important;
font-family: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
}
u + #body a, /* Gmail */
#MessageViewBody a /* Samsung Mail */
{
color: inherit;
text-decoration: none;
font-size: inherit;
font-family: inherit;
font-weight: inherit;
line-height: inherit;
}
/* What it does: Prevents Gmail from changing the text color in conversation threads. */
.im {
color: inherit !important;
}
/* What it does: Prevents Gmail from displaying an download button on large, non-linked images. */
.a6S {
display: none !important;
opacity: 0.01 !important;
}
/* If the above doesn't work, add a .g-img class to any image in question. */
img.g-img + div {
display:none !important;
}
/* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */
/* Create one of these media queries for each additional viewport size you'd like to fix */
/* iPhone 4, 4S, 5, 5S, 5C, and 5SE */
@media only screen and (min-device-width: 320px) and (max-device-width: 374px) {
u ~ div .email-container {
min-width: 320px !important;
}
}
/* iPhone 6, 6S, 7, 8, and X */
@media only screen and (min-device-width: 375px) and (max-device-width: 413px) {
u ~ div .email-container {
min-width: 375px !important;
}
}
/* iPhone 6+, 7+, and 8+ */
@media only screen and (min-device-width: 414px) {
u ~ div .email-container {
min-width: 414px !important;
}
}
</style>
<!-- What it does: Helps DPI scaling in Outlook 2007-2013 -->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<!-- CSS Reset : END -->
<!-- Progressive Enhancements : BEGIN -->
<style>
/* What it does: Hover styles for buttons and tags */
.s-btn__primary:hover {
background: #0077CC !important;
border-color: #0077CC !important;
}
.s-btn__white:hover {
background: #EFF0F1 !important;
border-color: #EFF0F1 !important;
}
.s-btn__outlined:hover {
background: rgba(0,119,204,.05) !important;
color: #005999 !important;
}
.s-tag:hover,
.post-tag:hover {
border-color: #cee0ed !important;
background: #cee0ed !important;
}
/* What it does: Styles markdown links that we can't write inline CSS for. */
.has-markdown a,
.has-markdown a:visited {
color: #0077CC !important;
text-decoration: none !important;
}
/* What it does: Styles markdown code blocks that we can't write inline CSS for. */
code {
padding: 1px 5px;
background-color: #EFF0F1;
color: #242729;
font-size: 13px;
line-height: inherit;
font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, sans-serif;
}
pre {
margin: 0 0 15px;
line-height: 17px;
background-color: #EFF0F1;
padding: 4px 8px;
border-radius: 3px;
overflow-x: auto;
}
pre code {
margin: 0 0 15px;
padding: 0;
line-height: 17px;
background-color: none;
}
/* What it does: Styles markdown blockquotes that we can't write inline CSS for. */
blockquote {
margin: 0 0 15px;
padding: 4px 10px;
background-color: #FFF8DC;
border-left: 2px solid #ffeb8e;
}
blockquote p {
padding: 4px 0;
margin: 0;
overflow-wrap: break-word;
}
/* What it does: Rounds corners in email clients that support it */
.bar {
border-radius: 5px;
}
.btr {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.bbr {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
@media screen and (max-width: 680px) {
/* What it does: Forces table cells into full-width rows. */
.stack-column,
.stack-column-center {
display: block !important;
width: 100% !important;
max-width: 100% !important;
direction: ltr !important;
}
/* And center justify these ones. */
.stack-column-center {
text-align: center !important;
}
/* Hides things in small viewports. */
.hide-on-mobile {
display: none !important;
max-height: 0 !important;
overflow: hidden !important;
visibility: hidden !important;
}
/* What it does: Utility classes to reduce spacing for smaller viewports. */
.sm-p-none {padding: 0 !important;}
.sm-pt-none {padding-top: 0 !important;}
.sm-pb-none {padding-bottom: 0 !important;}
.sm-pr-none {padding-right: 0 !important;}
.sm-pl-none {padding-left: 0 !important;}
.sm-px-none {padding-left: 0 !important; padding-right: 0 !important;}
.sm-py-none {padding-top: 0 !important; padding-bottom: 0 !important;}
.sm-p {padding: 20px !important;}
.sm-pt {padding-top: 20px !important;}
.sm-pb {padding-bottom: 20px !important;}
.sm-pr {padding-right: 20px !important;}
.sm-pl {padding-left: 20px !important;}
.sm-px {padding-left: 20px !important; padding-right: 20px !important;}
.sm-py {padding-top: 20px !important; padding-bottom: 20px !important;}
.sm-mb {margin-bottom: 20px !important;}
/* What it does: Utility classes to kill border radius for smaller viewports. Used mainly on the email's main container(s). */
.bar,
.btr,
.bbr {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
</style>
<!-- Progressive Enhancements : END -->
</head>
<!--
The email background color is defined in three places, just below. If you change one, remember to change the others.
1. body tag: for most email clients
2. center tag: for Gmail and Inbox mobile apps and web versions of Gmail, GSuite, Inbox, Yahoo, AOL, Libero, Comcast, freenet, Mail.ru, Orange.fr
3. mso conditional: For Windows 10 Mail
-->
<body width="100%" style="margin: 0; padding: 0 !important; background: #f3f3f5; mso-line-height-rule: exactly;">
<center style="width: 100%; background: #f3f3f5;">
<!--[if mso | IE]>
<table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="background-color: #f3f3f5;">
<tr>
<td>
<![endif]-->
<!-- Visually Hidden Preview Text : BEGIN -->
<div style="display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;">
LfK! - Mail test
</div>
<!-- Visually Hidden Preview Text : END -->
<div class="email-container" style="max-width: 680px; margin: 0 auto;">
<!--[if mso]>
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="680" align="center">
<tr>
<td>
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="max-width: 680px; width:100%">
<tr>
<td style="padding: 30px; background-color: #ffffff;" class="sm-p bar">
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
<tr>
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
<h1 style="font-weight: bold; font-size: 27px; line-height: 27px; color: #0C0D0E; margin: 0 0 15px 0;">LfK!</h1>
</td>
</tr>
<tr>
<td style="padding-bottom: 15px; font-family: arial, sans-serif; font-size: 15px; line-height: 21px; color: #3C3F44; text-align: left;">
<h1 style="font-weight: bold; font-size: 21px; line-height: 21px; color: #0C0D0E; margin: 0 0 15px 0;">Test mail</h1>
<p style="margin: 0 0 15px;" class="has-markdown">This is a test mail triggered by an admin in the LfK! backend.</p>
</td>
</tr>
</table>
</td>
</tr>
<!-----------------------------
EMAIL BODY : END
------------------------------>
<!-- Footer : BEGIN -->
<tr>
<td style="padding: 30px;" class="sm-p">
<table align="left" border="0" cellpadding="0" cellspacing="0" role="presentation" width="100%">
<!-- Subscription Info : BEGIN -->
<tr>
<td style="padding-bottom: 10px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
Copyright © {{copyright_owner}}. All rights reserved.
</td>
</tr>
<tr>
<td style="font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">
<a href="{{link_imprint}}"
style="color: #9199A1; text-decoration: underline;">Imprint</a>&nbsp;&nbsp;&nbsp;&nbsp;
<a href="{{link_privacy}}" style="color: #9199A1; text-decoration: underline;">Privacy</a>
</td>
</tr>
<!-- Subscription Info : BEGIN -->
<!-- HR line : BEGIN -->
<tr>
<td style="padding: 30px 0;" width="100%" class="sm-py">
<table aria-hidden="true" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%">
<tr>
<td height="1" width="100%" style="font-size: 0; line-height: 0; border-top: 1px solid #D6D8DB;">&nbsp;</td>
</tr>
</table>
</td>
</tr>
<!-- HR line : END -->
<tr>
<td style="padding-bottom: 5px; font-size: 12px; line-height: 15px; font-family: arial, sans-serif; color: #9199A1; text-align: left;">This mail was sent to <strong>{{recipient_mail}}</strong> because someone request a mail test for this mail address.</td>
</tr>
<!-- Sender Info : END -->
</table>
</td>
</tr>
<!-- Footer : END -->
</table>
</div>
<!--[if mso | IE]>
</td>
</tr>
</table>
<![endif]-->
</center>
</body>
</html>

View File

@@ -0,0 +1,8 @@
LfK! - Mail test.
This is a test mail triggered by an admin in the LfK! backend.
Copyright © {{copyright_owner}}. All rights reserved.
Imprint: {{link_imprint}} | Privacy: {{link_privacy}}
This mail was sent to {{recipient_mail}} because someone requested a mail test for this mail address.

View File

@@ -15,7 +15,7 @@ beforeAll(async () => {
"lastname": "demo_reset",
"username": "demo_reset",
"password": "demo_reset",
"email": "demo_reset@dev.lauf-fuer-kaya.de"
"email": "demo_reset1@dev.lauf-fuer-kaya.de"
}, {
headers: { "authorization": "Bearer " + res_login.data["access_token"] },
validateStatus: undefined
@@ -26,7 +26,7 @@ beforeAll(async () => {
"lastname": "demo_reset2",
"username": "demo_reset2",
"password": "demo_reset2",
"email": "demo_reset1@dev.lauf-fuer-kaya.de"
"email": "demo_reset2@dev.lauf-fuer-kaya.de"
}, {
headers: { "authorization": "Bearer " + res_login.data["access_token"] },
validateStatus: undefined
@@ -36,24 +36,16 @@ beforeAll(async () => {
describe('POST /api/auth/reset valid', () => {
let reset_token;
it('valid reset token request should return 200', async () => {
const res1 = await axios.post(base + '/api/auth/reset', { username: "demo_reset" });
const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset1@dev.lauf-fuer-kaya.de" });
reset_token = res1.data.resetToken;
expect(res1.status).toEqual(200);
});
it('valid password reset should return 200', async () => {
const res2 = await axios.post(base + '/api/auth/reset/' + reset_token, { password: "demo" }, axios_config);
expect(res2.status).toEqual(200);
});
it('valid login after reset should return 200', async () => {
const res = await axios.post(base + '/api/auth/login', { username: "demo_reset", password: "demo" });
expect(res.status).toEqual(200);
});
});
// ---------------
describe('POST /api/auth/reset invalid requests', () => {
it('request another password reset before the timeout should return 406', async () => {
const res1 = await axios.post(base + '/api/auth/reset', { username: "demo_reset2" }, axios_config);
const res2 = await axios.post(base + '/api/auth/reset', { username: "demo_reset2" }, axios_config);
const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset2@dev.lauf-fuer-kaya.de" }, axios_config);
const res2 = await axios.post(base + '/api/auth/reset', { email: "demo_reset2@dev.lauf-fuer-kaya.de" }, axios_config);
expect(res2.status).toEqual(406);
});
});
@@ -63,9 +55,9 @@ describe('POST /api/auth/reset invalid token', () => {
const res2 = await axios.post(base + '/api/auth/reset/' + "123123", { password: "demo" }, axios_config);
expect(res2.status).toEqual(401);
});
it('providing no reset token should return 404', async () => {
it('providing no reset token should return 400', async () => {
const res2 = await axios.post(base + '/api/auth/reset/' + "", { password: "demo" }, axios_config);
expect(res2.status).toEqual(404);
expect(res2.status).toEqual(400);
});
});
// ---------------

View File

@@ -34,7 +34,8 @@ describe('POST /api/cards successfully (without runner)', () => {
delete res.data.code;
expect(res.data).toEqual({
"runner": null,
"enabled": true
"enabled": true,
"responseType": "RUNNERCARD"
});
});
it('creating a disabled card should return 200', async () => {
@@ -47,7 +48,8 @@ describe('POST /api/cards successfully (without runner)', () => {
delete res.data.code;
expect(res.data).toEqual({
"runner": null,
"enabled": false
"enabled": false,
"responseType": "RUNNERCARD"
});
});
it('creating a enabled card should return 200', async () => {
@@ -60,7 +62,8 @@ describe('POST /api/cards successfully (without runner)', () => {
delete res.data.code;
expect(res.data).toEqual({
"runner": null,
"enabled": true
"enabled": true,
"responseType": "RUNNERCARD"
});
});
});
@@ -69,7 +72,7 @@ describe('POST /api/cards successfully (with runner)', () => {
let added_org;
let added_runner;
it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', {
const res1 = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res1.data
@@ -97,7 +100,8 @@ describe('POST /api/cards successfully (with runner)', () => {
delete res.data.code;
expect(res.data).toEqual({
"runner": added_runner,
"enabled": true
"enabled": true,
"responseType": "RUNNERCARD"
});
});
it('creating a card with runner (no optional params) should return 200', async () => {
@@ -110,7 +114,8 @@ describe('POST /api/cards successfully (with runner)', () => {
delete res.data.code;
expect(res.data).toEqual({
"runner": added_runner,
"enabled": true
"enabled": true,
"responseType": "RUNNERCARD"
});
});
it('creating a enabled card with runner should return 200', async () => {
@@ -124,7 +129,8 @@ describe('POST /api/cards successfully (with runner)', () => {
delete res.data.code;
expect(res.data).toEqual({
"runner": added_runner,
"enabled": true
"enabled": true,
"responseType": "RUNNERCARD"
});
});
it('creating a disabled card with runner should return 200', async () => {
@@ -138,7 +144,8 @@ describe('POST /api/cards successfully (with runner)', () => {
delete res.data.code;
expect(res.data).toEqual({
"runner": added_runner,
"enabled": false
"enabled": false,
"responseType": "RUNNERCARD"
});
});
});

View File

@@ -50,7 +50,7 @@ describe('adding + updating card.runner successfully', () => {
let added_runner2;
let added_card;
it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', {
const res1 = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res1.data
@@ -96,7 +96,8 @@ describe('adding + updating card.runner successfully', () => {
"id": added_card.id,
"runner": added_runner,
"enabled": true,
"code": added_card.code
"code": added_card.code,
"responseType": "RUNNERCARD"
});
});
it('valid runner update (change runner) should return 200', async () => {
@@ -110,7 +111,8 @@ describe('adding + updating card.runner successfully', () => {
"id": added_card.id,
"runner": added_runner2,
"enabled": true,
"code": added_card.code
"code": added_card.code,
"responseType": "RUNNERCARD"
});
});
});
@@ -142,7 +144,8 @@ describe('adding + updating other values successfully', () => {
"id": added_card.id,
"runner": null,
"enabled": false,
"code": added_card.code
"code": added_card.code,
"responseType": "RUNNERCARD"
});
});
it('valid enable update should return 200', async () => {
@@ -156,7 +159,8 @@ describe('adding + updating other values successfully', () => {
"id": added_card.id,
"runner": null,
"enabled": true,
"code": added_card.code
"code": added_card.code,
"responseType": "RUNNERCARD"
});
});
});

View File

@@ -108,7 +108,7 @@ describe('POST /api/contacts working (with group)', () => {
let added_team;
let added_contact;
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
delete res.data.contact;
@@ -123,7 +123,6 @@ describe('POST /api/contacts working (with group)', () => {
"parentGroup": added_org.id
}, axios_config);
delete res.data.contact;
delete res.data.parentGroup;
added_team = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
@@ -137,6 +136,7 @@ describe('POST /api/contacts working (with group)', () => {
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
delete res.data.id;
delete res.data.groups[0].teams;
expect(res.data).toEqual({
"firstname": "first",
"middlename": null,
@@ -150,7 +150,8 @@ describe('POST /api/contacts working (with group)', () => {
"city": null,
"country": null
},
"groups": [added_org]
"groups": [added_org],
"responseType": "GROUPCONTACT"
});
});
it('creating a new contact with a valid team should return 200', async () => {
@@ -175,7 +176,8 @@ describe('POST /api/contacts working (with group)', () => {
"city": null,
"country": null
},
"groups": [added_team]
"groups": [added_team],
"responseType": "GROUPCONTACT"
});
});
it('creating a new contact with a valid org and team should return 200', async () => {
@@ -188,6 +190,7 @@ describe('POST /api/contacts working (with group)', () => {
expect(res.headers['content-type']).toContain("application/json");
added_contact = res.data
delete res.data.id;
delete res.data.groups[0].teams;
expect(res.data).toEqual({
"firstname": "first",
"middlename": null,
@@ -201,7 +204,8 @@ describe('POST /api/contacts working (with group)', () => {
"city": null,
"country": null
},
"groups": [added_org, added_team]
"groups": [added_org, added_team],
"responseType": "GROUPCONTACT"
});
});
it('checking if the added team\'s contact is the new contact should return 200', async () => {

View File

@@ -50,7 +50,7 @@ describe('add+delete (with org)', () => {
let added_org;
let added_contact;
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
delete res.data.contact;
@@ -88,7 +88,7 @@ describe('add+delete (with team)', () => {
let added_team;
let added_contact;
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
delete res.data.contact;
@@ -137,7 +137,7 @@ describe('add+delete (with org&team)', () => {
let added_team;
let added_contact;
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
delete res.data.contact;

View File

@@ -59,7 +59,7 @@ describe('Update contact group after adding (should work)', () => {
let added_team;
let added_contact;
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
delete res.data.contact;
@@ -74,7 +74,6 @@ describe('Update contact group after adding (should work)', () => {
"parentGroup": added_org.id
}, axios_config);
delete res.data.contact;
delete res.data.parentGroup;
added_team = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
@@ -87,7 +86,8 @@ describe('Update contact group after adding (should work)', () => {
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
added_contact = res.data
added_contact = res.data;
delete res.data.groups[0].teams;
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
@@ -102,7 +102,8 @@ describe('Update contact group after adding (should work)', () => {
"city": null,
"country": null
},
"groups": [added_org]
"groups": [added_org],
"responseType": "GROUPCONTACT"
});
});
it('valid group update to single team should return 200', async () => {
@@ -128,7 +129,8 @@ describe('Update contact group after adding (should work)', () => {
"city": null,
"country": null
},
"groups": [added_team]
"groups": [added_team],
"responseType": "GROUPCONTACT"
});
});
it('valid group update to org and team should return 200', async () => {
@@ -140,6 +142,7 @@ describe('Update contact group after adding (should work)', () => {
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
delete res.data.groups[0].teams;
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
@@ -154,7 +157,8 @@ describe('Update contact group after adding (should work)', () => {
"city": null,
"country": null
},
"groups": [added_org, added_team]
"groups": [added_org, added_team],
"responseType": "GROUPCONTACT"
});
});
it('valid group update to none should return 200', async () => {
@@ -180,7 +184,8 @@ describe('Update contact group after adding (should work)', () => {
"city": null,
"country": null
},
"groups": []
"groups": [],
"responseType": "GROUPCONTACT"
});
});
});
@@ -189,7 +194,7 @@ describe('Update contact group invalid after adding (should fail)', () => {
let added_org;
let added_contact;
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
delete res.data.contact;
@@ -207,6 +212,7 @@ describe('Update contact group invalid after adding (should fail)', () => {
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
added_contact = res.data
delete res.data.groups[0].teams;
expect(res.data).toEqual({
"id": res.data.id,
"firstname": "first",
@@ -221,7 +227,8 @@ describe('Update contact group invalid after adding (should fail)', () => {
"city": null,
"country": null
},
"groups": [added_org]
"groups": [added_org],
"responseType": "GROUPCONTACT"
});
});
it('invalid group update to single team should return 404', async () => {

View File

@@ -83,7 +83,7 @@ describe('POST /api/donations/distance illegally', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data
@@ -179,7 +179,8 @@ describe('POST /api/donations/fixed successfully', () => {
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"donor": added_donor,
"amount": 1000
"amount": 1000,
"responseType": "DONATION"
});
});
});
@@ -199,7 +200,7 @@ describe('POST /api/donations/distance successfully', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data
@@ -230,7 +231,8 @@ describe('POST /api/donations/distance successfully', () => {
"donor": added_donor,
"amountPerDistance": 100,
"runner": added_runner,
"amount": 0
"amount": 0,
"responseType": "DISTANCEDONATION"
})
});
});

View File

@@ -70,7 +70,7 @@ describe('DELETE distance donation', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data

View File

@@ -73,7 +73,7 @@ describe('adding + getting distance donation', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data

View File

@@ -84,7 +84,7 @@ describe('adding + updating distance donation illegally', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data
@@ -253,7 +253,7 @@ describe('adding + updating distance donation valid', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data
@@ -323,7 +323,6 @@ describe('adding + updating distance donation valid', () => {
"amountPerDistance": 69,
"donor": added_donor.id
}, axios_config);
delete res.data.runner.group;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data.runner).toEqual(added_runner2);

View File

@@ -60,7 +60,7 @@ describe('DELETE donor with donations invalid', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data
@@ -115,7 +115,7 @@ describe('DELETE donor with donations valid', () => {
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res.data

View File

@@ -0,0 +1,22 @@
import axios from 'axios';
import { config } from '../../config';
const base = "http://localhost:" + config.internal_port
let access_token;
let axios_config;
beforeAll(async () => {
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
access_token = res.data["access_token"];
axios_config = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined
};
});
describe('POST /mails/test valid', () => {
it('test mail request should return 200', async () => {
const res1 = await axios.post(base + '/api/mails/test', null, axios_config);
});
});

View File

@@ -14,24 +14,24 @@ beforeAll(async () => {
};
});
describe('GET /api/organisations', () => {
describe('GET /api/organizations', () => {
it('basic get should return 200', async () => {
const res = await axios.get(base + '/api/organisations', axios_config);
const res = await axios.get(base + '/api/organizations', axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
});
// ---------------
describe('POST /api/organisations', () => {
describe('POST /api/organizations', () => {
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('creating a new org with without a name should return 400', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": null
}, axios_config);
expect(res.status).toEqual(400);
@@ -41,21 +41,20 @@ describe('POST /api/organisations', () => {
// ---------------
describe('adding + getting from all orgs', () => {
it('creating a new org with just a name should return 200', async () => {
const res = await axios.post(base + '/api/organisations', {
const res = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
it('check if org was added', async () => {
const res = await axios.get(base + '/api/organisations', axios_config);
const res = await axios.get(base + '/api/organizations', axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
let added_org = res.data[res.data.length - 1]
delete added_org.id
expect(added_org).toEqual({
"name": "test123",
"contact": null,
"address": {
"address1": null,
"address2": null,
@@ -63,7 +62,9 @@ describe('adding + getting from all orgs', () => {
"country": null,
"postalcode": null,
},
"teams": []
"registrationEnabled": false,
"teams": [],
"responseType": "RUNNERORGANIZATION"
})
});
});
@@ -71,7 +72,7 @@ describe('adding + getting from all orgs', () => {
describe('adding + getting explicitly', () => {
let added_org_id
it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', {
const res1 = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
let added_org = res1.data
@@ -80,7 +81,7 @@ describe('adding + getting explicitly', () => {
expect(res1.headers['content-type']).toContain("application/json")
});
it('check if org was added', async () => {
const res2 = await axios.get(base + '/api/organisations/' + added_org_id, axios_config);
const res2 = await axios.get(base + '/api/organizations/' + added_org_id, axios_config);
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
let added_org2 = res2.data
@@ -88,7 +89,6 @@ describe('adding + getting explicitly', () => {
delete added_org2.id
expect(added_org2).toEqual({
"name": "test123",
"contact": null,
"address": {
"address1": null,
"address2": null,
@@ -96,7 +96,9 @@ describe('adding + getting explicitly', () => {
"country": null,
"postalcode": null,
},
"teams": []
"registrationEnabled": false,
"teams": [],
"responseType": "RUNNERORGANIZATION"
})
});
});

View File

@@ -17,7 +17,7 @@ beforeAll(async () => {
// ---------------
describe('adding + deletion (non-existant)', () => {
it('delete', async () => {
const res2 = await axios.delete(base + '/api/organisations/0', axios_config);
const res2 = await axios.delete(base + '/api/organizations/0', axios_config);
expect(res2.status).toEqual(204);
});
});
@@ -26,7 +26,7 @@ describe('adding + deletion (successfull)', () => {
let added_org_id
let added_org
it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', {
const res1 = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res1.data
@@ -35,7 +35,7 @@ describe('adding + deletion (successfull)', () => {
expect(res1.headers['content-type']).toContain("application/json")
});
it('delete', async () => {
const res2 = await axios.delete(base + '/api/organisations/' + added_org_id, axios_config);
const res2 = await axios.delete(base + '/api/organizations/' + added_org_id, axios_config);
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
let added_org2 = res2.data
@@ -43,7 +43,6 @@ describe('adding + deletion (successfull)', () => {
delete added_org2.id
expect(added_org2).toEqual({
"name": "test123",
"contact": null,
"address": {
"address1": null,
"address2": null,
@@ -51,11 +50,13 @@ describe('adding + deletion (successfull)', () => {
"country": null,
"postalcode": null,
},
"teams": []
"registrationEnabled": false,
"teams": [],
"responseType": "RUNNERORGANIZATION"
});
});
it('check if org really was deleted', async () => {
const res3 = await axios.get(base + '/api/organisations/' + added_org_id, axios_config);
const res3 = await axios.get(base + '/api/organizations/' + added_org_id, axios_config);
expect(res3.status).toEqual(404);
expect(res3.headers['content-type']).toContain("application/json")
});
@@ -67,7 +68,7 @@ describe('adding + deletion with teams still existing (without force)', () => {
let added_team;
let added_team_id
it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', {
const res1 = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res1.data;
@@ -86,7 +87,7 @@ describe('adding + deletion with teams still existing (without force)', () => {
expect(res2.headers['content-type']).toContain("application/json")
});
it('delete org - this should fail with a 406', async () => {
const res2 = await axios.delete(base + '/api/organisations/' + added_org_id, axios_config);
const res2 = await axios.delete(base + '/api/organizations/' + added_org_id, axios_config);
expect(res2.status).toEqual(406);
expect(res2.headers['content-type']).toContain("application/json")
});
@@ -98,7 +99,7 @@ describe('adding + deletion with teams still existing (with force)', () => {
let added_team;
let added_team_id
it('creating a new org with just a name should return 200', async () => {
const res1 = await axios.post(base + '/api/organisations', {
const res1 = await axios.post(base + '/api/organizations', {
"name": "test123"
}, axios_config);
added_org = res1.data;
@@ -117,7 +118,7 @@ describe('adding + deletion with teams still existing (with force)', () => {
expect(res2.headers['content-type']).toContain("application/json")
});
it('delete', async () => {
const res2 = await axios.delete(base + '/api/organisations/' + added_org_id + '?force=true', axios_config);
const res2 = await axios.delete(base + '/api/organizations/' + added_org_id + '?force=true', axios_config);
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
let added_org2 = res2.data
@@ -126,7 +127,6 @@ describe('adding + deletion with teams still existing (with force)', () => {
delete added_org2.teams;
expect(added_org2).toEqual({
"name": "test123",
"contact": null,
"address": {
"address1": null,
"address2": null,
@@ -134,10 +134,12 @@ describe('adding + deletion with teams still existing (with force)', () => {
"country": null,
"postalcode": null,
},
"registrationEnabled": false,
"responseType": "RUNNERORGANIZATION"
});
});
it('check if org really was deleted', async () => {
const res3 = await axios.get(base + '/api/organisations/' + added_org_id, axios_config);
const res3 = await axios.get(base + '/api/organizations/' + added_org_id, axios_config);
expect(res3.status).toEqual(404);
expect(res3.headers['content-type']).toContain("application/json")
});

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