Compare commits

...

242 Commits

Author SHA1 Message Date
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
dd3d93edc7 Merge pull request 'Alpha Release 0.2.0' (#109) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #109
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-20 17:05:12 +00:00
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
da9a359251 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-19 19:10:15 +00:00
0661729e5f Merge branch 'dev' of git.odit.services:lfk/backend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-19 20:09:36 +01:00
ddafd90d3e 🚀Bumped version to v0.2.0 2021-01-19 20:09:30 +01:00
8960aa5545 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-19 19:09:11 +00:00
a0c2b5ade8 Merge pull request 'Implemented group contacts feature/104-contacts' (#108) from feature/104-contacts into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #108
2021-01-19 19:08:53 +00:00
a1acd3519f Adjusted env sample
All checks were successful
continuous-integration/drone/pr Build is passing
ref #104 ref #105
2021-01-19 19:33:11 +01:00
c3d008ec0f Updated contact update tests
All checks were successful
continuous-integration/drone/pr Build is passing
ref #104
2021-01-19 19:32:39 +01:00
8ae53f1c49 Updated contact delete tests
ref #104
2021-01-19 19:12:53 +01:00
179c2a5157 Fixed contact cascading
ref #104
2021-01-19 19:04:46 +01:00
dd7e5dae36 Added contact delete tests
ref #104
2021-01-19 19:04:09 +01:00
e165f01930 Added contact add valid tests
ref #104
2021-01-19 18:48:37 +01:00
940d62cde4 Added contact add invalid tests
ref #104
2021-01-19 18:14:09 +01:00
b002cf2df1 Added contact get tests
ref #104
2021-01-19 18:13:39 +01:00
56c73c2555 Added openapi description about non-deletion
ref #104
2021-01-19 18:03:29 +01:00
28fb9834e1 Implemented contact updateing
ref #104
2021-01-19 18:01:37 +01:00
6b4b16c13b Added missing id property 2021-01-19 18:00:45 +01:00
d743f7ee12 Renamed controller to better fit the overall nameing scheme
ref #104
2021-01-19 17:58:03 +01:00
a4e8311cbd Updated comments
ref #104
2021-01-19 17:57:15 +01:00
c172aa8bf8 Added a contact update class
ref #104
2021-01-19 17:55:56 +01:00
d1926fe372 Merge branch 'feature/104-contacts' of git.odit.services:lfk/backend into feature/104-contacts 2021-01-19 17:53:02 +01:00
2b658ac381 Fixed column not getting resolved
ref #104
2021-01-19 17:52:59 +01:00
321d291b4b Fixed column not getting resolved 2021-01-19 17:52:51 +01:00
2eb26e4e38 Fixed push undefined eror
ref #104
2021-01-19 17:41:00 +01:00
3b06d1a6ef Implemented contact group setting on creation
ref #104
2021-01-19 17:29:52 +01:00
de824375d3 Fixed key null constraint
ref #104
2021-01-19 17:27:43 +01:00
11af9c02d9 Implemented contact posting
ref #104
2021-01-19 17:14:05 +01:00
09e429fc67 Added address to contact response
ref #104
2021-01-19 17:13:46 +01:00
703b4f89a6 Merge branch 'dev' into feature/104-contacts 2021-01-19 16:44:34 +01:00
32e054eb84 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-19 15:37:52 +00:00
5e368552ea Merge pull request 'Fully implemented addresses feature/105-addresses' (#107) from feature/105-addresses into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #107
2021-01-19 15:37:35 +00:00
0379786cbd Implemented contact deletion
ref #104
2021-01-19 16:09:23 +01:00
a9a5eb6735 Updated the contact errors
ref #104
2021-01-19 16:06:42 +01:00
ab70f7e498 Implemented the get endpoints
ref #104
2021-01-19 16:05:35 +01:00
1407fe36f3 Added a contact response class
ref #104
2021-01-19 16:02:13 +01:00
d12801e34d Added contact permission target
ref #104
2021-01-19 15:56:55 +01:00
3e7190e279 Added barebones contact controller from donor-controller
ref #104
2021-01-19 15:56:03 +01:00
41423feffe Merge branch 'dev' into feature/105-addresses
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-19 14:51:07 +00:00
30b585c0c1 Set country code for the ci env to DE
All checks were successful
continuous-integration/drone/pr Build is passing
ref #105
2021-01-19 15:49:35 +01:00
a3c93f0d39 Cleaned up var names
Some checks failed
continuous-integration/drone/pr Build is failing
ref #105
2021-01-19 15:48:06 +01:00
f53894b16a 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-16 20:33:37 +00:00
7533c349ef Merge pull request 'Alpha Release 0.1.1 - Hotfix release' (#106) from dev into main
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #106
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-16 20:32:39 +00:00
91569ced40 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-16 20:30:47 +00:00
f9ae778b21 Merge branch 'main' into dev
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-16 20:30:28 +00:00
427dfaafab Added address update ivalid tests
Some checks failed
continuous-integration/drone/pr Build is failing
ref #105
2021-01-16 21:26:45 +01:00
ae589aeb54 Merge branch 'dev' into feature/105-addresses
# Conflicts:
#	src/errors/AddressErrors.ts
#	src/models/actions/create/CreateAddress.ts
#	src/models/actions/create/CreateDonor.ts
#	src/models/actions/create/CreateGroupContact.ts
#	src/models/actions/create/CreateParticipant.ts
#	src/models/actions/create/CreateRunner.ts
#	src/models/actions/create/CreateRunnerOrganisation.ts
#	src/models/actions/update/UpdateDonor.ts
#	src/models/actions/update/UpdateRunner.ts
#	src/models/actions/update/UpdateRunnerOrganisation.ts
#	src/models/entities/Address.ts
#	src/models/entities/IAddressUser.ts
#	src/models/entities/RunnerOrganisation.ts
#	src/models/responses/ResponseParticipant.ts
#	src/tests/donors/donor_add.spec.ts
#	src/tests/donors/donor_update.spec.ts
#	src/tests/runnerOrgs/org_add.spec.ts
#	src/tests/runnerOrgs/org_delete.spec.ts
#	src/tests/runnerOrgs/org_update.spec.ts
#	src/tests/runnerTeams/team_update.spec.ts
#	src/tests/runners/runner_update.spec.ts
2021-01-16 21:15:02 +01:00
1b9d2969eb 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-16 20:06:31 +00:00
daffbcde72 Merge branch 'dev' of git.odit.services:lfk/backend into dev
Some checks reported errors
continuous-integration/drone/push Build was killed
# Conflicts:
#	CHANGELOG.md
2021-01-16 21:06:12 +01:00
9445c6f21e 🚀Bumped version to v0.1.1 2021-01-16 21:05:43 +01:00
6febb99499 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-16 20:00:06 +00:00
6e6979cfe3 Hotfix: Missing relation bug
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-16 20:59:48 +01:00
230cdb0e37 Added address update valid tests
ref #105
2021-01-16 20:37:48 +01:00
ce450e9b6d Merge branch 'dev' into feature/105-addresses 2021-01-16 20:33:28 +01:00
de36a24191 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-16 19:32:57 +00:00
b167ba07f7 Hotfix: Missing relation bug
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-16 20:32:40 +01:00
4d40225a44 Added first address update tests
ref #105
2021-01-16 20:26:58 +01:00
57b9c2babc Implemented adress deletion (through reset)
ref #105
2021-01-16 20:19:09 +01:00
9dc9ce37d8 Implemented deep address validation
ref #105
2021-01-16 20:12:17 +01:00
f245840cde Implemented postal code validation for the validaton function
ref #105
2021-01-16 18:59:06 +01:00
4824547dde Fixed donor address check
ref #105
2021-01-16 18:52:57 +01:00
8dbee32eee Test's now accept the new address format
ref #105
2021-01-16 18:34:53 +01:00
ae7c5ff0c3 Added address validity check
ref #105
2021-01-16 18:28:19 +01:00
2a465f88c5 Removed old create address class
ref #105
2021-01-16 17:03:05 +01:00
58ae9b589a Removed the address errors
ref #105
2021-01-16 16:58:55 +01:00
8bc01d3f24 Updated comments
ref #105
2021-01-16 16:57:58 +01:00
d0df5dd641 Switched the update classes over to the new address implementation
ref #105
2021-01-16 16:56:46 +01:00
2cd15d25e9 Switched the create classes over to the new address implementation
ref #105
2021-01-16 16:55:30 +01:00
dafac06bc8 Updated the responseclasses to use the new address implementation
ref #105
2021-01-16 16:53:18 +01:00
e2651728c5 Removed the IAddressUser Interface entity
ref #105 - It was only needed b/c addresses were implemented as their own class
2021-01-16 16:50:04 +01:00
673dea2e57 Removed (now useless) relations
ref #105
2021-01-16 16:48:20 +01:00
7fbe649dc9 Switched Address to embedded entity
ref #105
2021-01-16 16:45:49 +01:00
3766899c83 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-15 21:57:40 +00:00
a6c7d54fe7 Merge pull request 'User self-management feature/100-me_endpoints' (#103) from feature/100-me_endpoints into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #103
2021-01-15 21:57:21 +00:00
79bc04bec1 Merge branch 'dev' into feature/100-me_endpoints
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-15 21:56:57 +00:00
f9834b5f4d Moved the me endpoints to /users/me
All checks were successful
continuous-integration/drone/pr Build is passing
ref #100
2021-01-15 22:45:45 +01:00
fc7b8f4c16 Updated descriptions and responses
All checks were successful
continuous-integration/drone/pr Build is passing
ref #100
2021-01-15 22:43:22 +01:00
4f6e81677c Implemented getting own permissions
ref #100
2021-01-15 22:35:50 +01:00
6b7ecd3044 User deletion now requires confirmation
ref #100
2021-01-15 22:35:23 +01:00
8ef5f90abd Implemented the /me controller that allows a user to get and update themselves
ref #100
2021-01-15 22:28:18 +01:00
a334adffc6 Moved optional param to being optional
ref #100
2021-01-15 22:27:44 +01:00
f1db883609 Implemented a baisc user checker/getter
ref #100
2021-01-15 22:16:28 +01:00
e586a11e2a Created barebones file for the userchecker
ref #100
2021-01-15 21:57:39 +01:00
50b893f537 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-15 20:53:36 +00:00
02efb9a8e5 automaticly merge main into dev after building a latest image
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-15 21:53:20 +01:00
145 changed files with 5093 additions and 6451 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:
@@ -90,11 +89,20 @@ trigger:
kind: pipeline
type: docker
name: build:latest
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout dev
- git merge main
- git checkout main
- name: build latest
depends_on: ["clone"]
image: plugins/docker
depends_on: [clone]
settings:
username:
from_secret: DOCKER_REGISTRY_USER
@@ -104,6 +112,15 @@ steps:
tags:
- latest
registry: registry.odit.services
- name: push merge to repo
depends_on: ["clone"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: false
remote: git@git.odit.services:lfk/backend.git
ssh_key:
from_secret: GITLAB_SSHKEY
trigger:
branch:

View File

@@ -6,4 +6,4 @@ DB_USER=unused
DB_PASSWORD=bla
DB_NAME=./test.sqlite
NODE_ENV=dev
POSTALCODE_COUNTRYCODE=null
POSTALCODE_COUNTRYCODE=DE

View File

@@ -1,9 +1,10 @@
APP_PORT=4010
DB_TYPE=bla
DB_TYPE=sqlite
DB_HOST=bla
DB_PORT=bla
DB_USER=bla
DB_PASSWORD=bla
DB_NAME=bla
DB_NAME=./test.sqlite
NODE_ENV=production
POSTALCODE_COUNTRYCODE=null
POSTALCODE_COUNTRYCODE=DE
SEED_TEST_DATA=false

View File

@@ -2,8 +2,262 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v0.4.1](https://git.odit.services/lfk/backend/compare/v0.4.0...v0.4.1)
- 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)
- 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)
> 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)
- Cleaned up var names [`a3c93f0`](https://git.odit.services/lfk/backend/commit/a3c93f0d394833f1a6f78d862b094ca751c85561)
- Added address update ivalid tests [`427dfaa`](https://git.odit.services/lfk/backend/commit/427dfaafabd243e94aba27c2dec2705fd8ed5d64)
- Added barebones contact controller from donor-controller [`3e7190e`](https://git.odit.services/lfk/backend/commit/3e7190e279181c5f99d890ca141489b24908b904)
- Added first address update tests [`4d40225`](https://git.odit.services/lfk/backend/commit/4d40225a4491e8eb3f41ef0fd558a599f63729be)
- Added a contact update class [`c172aa8`](https://git.odit.services/lfk/backend/commit/c172aa8bf8083500828743ed696955a1fe3caef2)
- Added address update valid tests [`230cdb0`](https://git.odit.services/lfk/backend/commit/230cdb0e37e2b7a21e7feb156f2b91a69ad200fd)
- Implemented deep address validation [`9dc9ce3`](https://git.odit.services/lfk/backend/commit/9dc9ce37d8fbfc92842e4e05bbde68398324a186)
- Added contact add invalid tests [`940d62c`](https://git.odit.services/lfk/backend/commit/940d62cde4cf7be7780904d681a5e4c9efaa2ba5)
- Added a contact response class [`1407fe3`](https://git.odit.services/lfk/backend/commit/1407fe36f3637d6c53024c48788b318d985f8960)
- Removed old create address class [`2a465f8`](https://git.odit.services/lfk/backend/commit/2a465f88c58c0b4be3ecd99d96a04c177a40b312)
- Switched the create classes over to the new address implementation [`2cd15d2`](https://git.odit.services/lfk/backend/commit/2cd15d25e934a5439bfea4de901f136e360e17f6)
- Added contact get tests [`b002cf2`](https://git.odit.services/lfk/backend/commit/b002cf2df1eafc722fbfb51b3bffb02bee002305)
- Test's now accept the new address format [`8dbee32`](https://git.odit.services/lfk/backend/commit/8dbee32eeec8ee3d013e4446e8f53544ee4cb577)
- Implemented the get endpoints [`ab70f7e`](https://git.odit.services/lfk/backend/commit/ab70f7e49893344dde8f5af93f571a5d67818e19)
- Implemented contact deletion [`0379786`](https://git.odit.services/lfk/backend/commit/0379786cbda057ad95d709fa135d34beb0db8de1)
- Removed the IAddressUser Interface entity [`e265172`](https://git.odit.services/lfk/backend/commit/e2651728c5abf2273bf51a7652c51d55d8fa0a2f)
- 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)
- Removed the address errors [`58ae9b5`](https://git.odit.services/lfk/backend/commit/58ae9b589aef3580f4b8558c3d5ddbd7171e7915)
- Switched the update classes over to the new address implementation [`d0df5dd`](https://git.odit.services/lfk/backend/commit/d0df5dd641ac17f1fd8ad6bd8b46afa9dd2745c3)
- Updated the contact errors [`a9a5eb6`](https://git.odit.services/lfk/backend/commit/a9a5eb673570bb3c0d1a55bb1292e208493663bd)
- Implemented adress deletion (through reset) [`57b9c2b`](https://git.odit.services/lfk/backend/commit/57b9c2babcd68d69d1cbb240a86c2897717ba758)
- 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 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)
- 🧾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)
- Added openapi description about non-deletion [`56c73c2`](https://git.odit.services/lfk/backend/commit/56c73c2555d4d12ffb088ec5550667022d3a8694)
- Added contact permission target [`d12801e`](https://git.odit.services/lfk/backend/commit/d12801e34d57cec0e867f69123e3be39ed2fe2f5)
- Adjusted env sample [`a1acd35`](https://git.odit.services/lfk/backend/commit/a1acd3519f66965202da0cd15df61a807230619c)
- Renamed controller to better fit the overall nameing scheme [`d743f7e`](https://git.odit.services/lfk/backend/commit/d743f7ee1277256ada8fe39f900349ff2643118a)
- Fixed column not getting resolved [`2b658ac`](https://git.odit.services/lfk/backend/commit/2b658ac381f318cf37fc2051ea3e83976e3c5773)
- Fixed column not getting resolved [`321d291`](https://git.odit.services/lfk/backend/commit/321d291b4bf983ff4930cadecf3a013430a97649)
- Set country code for the ci env to DE [`30b585c`](https://git.odit.services/lfk/backend/commit/30b585c0c12b3b9818778110d33d5b3ab84d4192)
- Implemented postal code validation for the validaton function [`f245840`](https://git.odit.services/lfk/backend/commit/f245840cde5726611197b00730aca72ea133c427)
- Fixed push undefined eror [`2eb26e4`](https://git.odit.services/lfk/backend/commit/2eb26e4e381a97fd829a294501fa42ac7b712b56)
- Merge pull request 'Fully implemented addresses feature/105-addresses' (#107) from feature/105-addresses into dev [`5e36855`](https://git.odit.services/lfk/backend/commit/5e368552ea810ea1d3963b1207ba98f7b46c4abc)
#### [v0.1.1](https://git.odit.services/lfk/backend/compare/v0.1.0...v0.1.1)
> 16 January 2021
- Merge pull request 'Alpha Release 0.1.1 - Hotfix release' (#106) from dev into main [`7533c34`](https://git.odit.services/lfk/backend/commit/7533c349ef98ed328151259fca68621b3eb5fd98)
- 🚀Bumped version to v0.1.1 [`9445c6f`](https://git.odit.services/lfk/backend/commit/9445c6f21e376329b9200664a44a94ba1f1dd463)
- 🧾New changelog file version [CI SKIP] [skip ci] [`1b9d296`](https://git.odit.services/lfk/backend/commit/1b9d2969ebdca4dca84898b1e8307be7b781b90b)
- Implemented the /me controller that allows a user to get and update themselves [`8ef5f90`](https://git.odit.services/lfk/backend/commit/8ef5f90abda97a73d5c5a7767a144ac3fb5288c1)
- Implemented a baisc user checker/getter [`f1db883`](https://git.odit.services/lfk/backend/commit/f1db8836092269966a7f54e69b1f20c171e81b21)
- Implemented getting own permissions [`4f6e816`](https://git.odit.services/lfk/backend/commit/4f6e81677c81c852e735407295c634b43b317479)
- Hotfix: Missing relation bug [`6e6979c`](https://git.odit.services/lfk/backend/commit/6e6979cfe3660056cff6b9eabc194852234ac0a6)
- Hotfix: Missing relation bug [`b167ba0`](https://git.odit.services/lfk/backend/commit/b167ba07f79709a2c3b33c5546c52659c42863f3)
- automaticly merge main into dev after building a latest image [`02efb9a`](https://git.odit.services/lfk/backend/commit/02efb9a8e55831ecce4109e17b2f07a56e491fd5)
- User deletion now requires confirmation [`6b7ecd3`](https://git.odit.services/lfk/backend/commit/6b7ecd3044c45b2eed46ee5010bed4dab4f02df9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3766899`](https://git.odit.services/lfk/backend/commit/3766899c8393545a89986a98dafd542edc4a1d39)
- Created barebones file for the userchecker [`e586a11`](https://git.odit.services/lfk/backend/commit/e586a11e2ad42af9c9bb5d2a47f48e3306fe49b2)
- 🧾New changelog file version [CI SKIP] [skip ci] [`6febb99`](https://git.odit.services/lfk/backend/commit/6febb994990b4cab7ee54b0368f74dd95664bfdf)
- Updated descriptions and responses [`fc7b8f4`](https://git.odit.services/lfk/backend/commit/fc7b8f4c16cef0e72b04f096d5a17d4144b5feb7)
- 🧾New changelog file version [CI SKIP] [skip ci] [`50b893f`](https://git.odit.services/lfk/backend/commit/50b893f5370902ccc40f8bb45ed160103400f529)
- 🧾New changelog file version [CI SKIP] [skip ci] [`de36a24`](https://git.odit.services/lfk/backend/commit/de36a24191a8cdc4ff6b23637ea9f91109b59bbb)
- 🧾New changelog file version [CI SKIP] [skip ci] [`91569ce`](https://git.odit.services/lfk/backend/commit/91569ced40402a63017a90a01efaf48578b5c806)
- Merge pull request 'User self-management feature/100-me_endpoints' (#103) from feature/100-me_endpoints into dev [`a6c7d54`](https://git.odit.services/lfk/backend/commit/a6c7d54fe72ffe23add926afa0be150a7a370099)
- Moved the me endpoints to /users/me [`f9834b5`](https://git.odit.services/lfk/backend/commit/f9834b5f4d80b11ee5f7773b339dd421341c6e7f)
- Moved optional param to being optional [`a334adf`](https://git.odit.services/lfk/backend/commit/a334adffc6d07c8ab340263123e00a96f21acecb)
#### [v0.1.0](https://git.odit.services/lfk/backend/compare/v0.0.12...v0.1.0)
> 15 January 2021
- Merge pull request 'First feature version 0.1.0' (#102) from dev into main [`38b9a77`](https://git.odit.services/lfk/backend/commit/38b9a772cd2d1c1e6298ae449d07db7c555a00e9)
- Removed useless parts from functions and updated comments [`c05834f`](https://git.odit.services/lfk/backend/commit/c05834f2a13eb838efbf61be803e4e320561718e)
- Switched tests over to the new id-only schema [`d88fb18`](https://git.odit.services/lfk/backend/commit/d88fb183198e66cadf5290c1ef7b7e4ccedad4f0)
- 🧾New changelog file version [CI SKIP] [skip ci] [`0e119e4`](https://git.odit.services/lfk/backend/commit/0e119e48340cd0a602a08da727b480aa2fe5500c)
@@ -24,6 +278,7 @@ All notable changes to this project will be documented in this file. Dates are d
- 🧾New changelog file version [CI SKIP] [skip ci] [`dc6ad9c`](https://git.odit.services/lfk/backend/commit/dc6ad9cdd3d8f29ef9a15bf7ac61c7c55c57e9fb)
- 🧾New changelog file version [CI SKIP] [skip ci] [`d1a0bed`](https://git.odit.services/lfk/backend/commit/d1a0bed00e01a0e9d8ba1165e3c6ca3dd910bd00)
- Clarified comments [`1b799a6`](https://git.odit.services/lfk/backend/commit/1b799a697305791c3f67ac4a738c7287d1ac553e)
- 🧾New changelog file version [CI SKIP] [skip ci] [`6184304`](https://git.odit.services/lfk/backend/commit/618430433d03012c2cad5be6021cf1ea8fdf9624)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8218a45`](https://git.odit.services/lfk/backend/commit/8218a452bdf7550ec1eed2b0045e94ea4ae91d31)
- 🚀Bumped version to v0.1.0 [`80c5f9b`](https://git.odit.services/lfk/backend/commit/80c5f9b84de355b4408dcffd632589a9a0e4ad2e)
- 🧾New changelog file version [CI SKIP] [skip ci] [`79f46cb`](https://git.odit.services/lfk/backend/commit/79f46cb745e4cb4bdac7dbb6c6c2b8fdc9867592)

101
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,52 @@ Backend Server
yarn dev
```
### Generate Docs
### Run Tests
```bash
# Run tests once (server has to run)
yarn test
# Run test in watch mode (reruns on change)
yarn test:watch
# Run test in ci mode (automaticly starts the dev server)
yarn test:ci
```
### Use your own mail templates
> You use your own mail templates by replacing the default ones we provided (either in-code or by mounting them into the /app/static/mail_templates folder).
The mail templates always come in a .html and a .txt variant to provide compatability with legacy mail clients.
Currently the following templates exist:
* pw-reset.(html/txt)
### Generate Docs
```bash
yarn docs
```
### Docker w/ postgres 🐳
## ENV Vars
> You can provide them via .env file or docker env vars.
> You can use the `test:ci:generate_env` package script to generate a example env (uses [ethereal.email](https://ethereal.email) as the mailserver).
```bash
docker-compose up --build
```
| 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
## Recommended Editor
@@ -42,22 +76,19 @@ docker-compose up --build
### Recommended Extensions
- will be automatically recommended via ./vscode/extensions.json
* will be automatically recommended via ./vscode/extensions.json
## Branches
- main: Protected "release" branch
- dev: Current dev branch for merging the different features - only push for merges or minor changes!
- feature/xyz: Feature branches - `feature/issueid-title`
- bugfix/xyz: Branches for bugfixes - `bugfix/issueid-title` (no id for readme changes needed)
## File Structure
- src/models/entities\* - database models (typeorm entities)
- src/models/actions\* - actions models
- src/models/responses\* - response models
- src/controllers/\* - routing-controllers
- src/loaders/\* - loaders for the different init steps of the api server
- src/middlewares/\* - express middlewares (mainly auth r/n)
- src/errors/* - our custom (http) errors
- src/routes/\* - express routes for everything we don't do via routing-controllers (depreciated)
## Staging
### Branches & Tags
* vX.Y.Z: Release tags created from the main branch
* The version numbers follow the semver standard
* A new release tag automaticly triggers the release ci pipeline
* main: Protected "release" branch
* The latest tag of the docker image get's build from this
* New releases get created as tags from this
* dev: Current dev branch for merging the different feature branches and bugfixes
* The dev tag of the docker image get's build from this
* Only push minor changes to this branch!
* To merge a feature branch into this please create a pull request
* feature/xyz: Feature branches - nameing scheme: `feature/issueid-title`
* bugfix/xyz: Branches for bugfixes - nameing scheme:`bugfix/issueid-title`

View File

@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-backend",
"version": "0.1.0",
"version": "0.4.1",
"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,7 +77,9 @@
"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",

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

@@ -5,10 +5,12 @@ import { config, e as errors } from './config';
import loaders from "./loaders/index";
import authchecker from "./middlewares/authchecker";
import { ErrorHandler } from './middlewares/ErrorHandler';
import UserChecker from './middlewares/UserChecker';
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
const app = createExpressServer({
authorizationChecker: authchecker,
currentUserChecker: UserChecker,
middlewares: [ErrorHandler],
development: config.development,
cors: true,

View File

@@ -9,7 +9,14 @@ 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:4010",
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
}
let errors = 0
if (typeof config.internal_port !== "number") {
@@ -30,4 +37,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

@@ -2,17 +2,23 @@ import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routin
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")
@@ -82,13 +88,14 @@ export class AuthController {
}
@Post("/reset")
@ResponseSchema(ResponseAuth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@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) {
//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() };
const reset_token: String = await passwordReset.toResetToken();
await this.mailer.sendResetMail(passwordReset.email, reset_token);
return new ResponseEmpty();
}
@Post("/reset/:token")

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

@@ -0,0 +1,107 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnection, getConnectionManager, Repository } from 'typeorm';
import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
import { UpdateGroupContact } from '../models/actions/update/UpdateGroupContact';
import { GroupContact } from '../models/entities/GroupContact';
import { RunnerGroup } from '../models/entities/RunnerGroup';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseGroupContact } from '../models/responses/ResponseGroupContact';
@JsonController('/contacts')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class GroupContactController {
private contactRepository: Repository<GroupContact>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.contactRepository = getConnectionManager().get().getRepository(GroupContact);
}
@Get()
@Authorized("CONTACT:GET")
@ResponseSchema(ResponseGroupContact, { isArray: true })
@OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' })
async getAll() {
let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>();
const contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] });
contacts.forEach(contact => {
responseContacts.push(contact.toResponse());
});
return responseContacts;
}
@Get('/:id')
@Authorized("CONTACT:GET")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
@OnUndefined(GroupContactNotFoundError)
@OpenAPI({ description: 'Lists all information about the contact whose id got provided. <br> This includes the contact\'s associated groups.' })
async getOne(@Param('id') id: number) {
let contact = await this.contactRepository.findOne({ id: id }, { relations: ['groups', 'groups.parentGroup'] })
if (!contact) { throw new GroupContactNotFoundError(); }
return contact.toResponse();
}
@Post()
@Authorized("CONTACT:CREATE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new contact.' })
async post(@Body({ validate: true }) createContact: CreateGroupContact) {
let contact;
try {
contact = await createContact.toEntity();
} catch (error) {
throw error;
}
contact = await this.contactRepository.save(contact)
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
}
@Put('/:id')
@Authorized("CONTACT:UPDATE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
@ResponseSchema(GroupContactIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@OpenAPI({ description: "Update the contact whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateGroupContact) {
let oldContact = await this.contactRepository.findOne({ id: id });
if (!oldContact) {
throw new GroupContactNotFoundError();
}
if (oldContact.id != contact.id) {
throw new GroupContactIdsNotMatchingError();
}
await this.contactRepository.save(await contact.update(oldContact));
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
}
@Delete('/:id')
@Authorized("CONTACT:DELETE")
@ResponseSchema(ResponseGroupContact)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the contact whose id you provided. <br> If no contact with this id exists it will just return 204(no content). <br> This won\'t delete any groups associated with the contact.' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let contact = await this.contactRepository.findOne({ id: id });
if (!contact) { return null; }
const responseContact = await this.contactRepository.findOne(contact, { relations: ['groups', 'groups.parentGroup'] });
for (let group of responseContact.groups) {
group.contact = null;
await getConnection().getRepository(RunnerGroup).save(group);
}
await this.contactRepository.delete(contact);
return responseContact.toResponse();
}
}

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

@@ -0,0 +1,86 @@
import { Body, CurrentUser, Delete, Get, JsonController, OnUndefined, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UpdateUser } from '../models/actions/update/UpdateUser';
import { User } from '../models/entities/User';
import { ResponseUser } from '../models/responses/ResponseUser';
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
import { PermissionController } from './PermissionController';
@JsonController('/users/me')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class MeController {
private userRepository: Repository<User>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.userRepository = getConnectionManager().get().getRepository(User);
}
@Get('/')
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all information about yourself.' })
async get(@CurrentUser() currentUser: User) {
let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUser(user);
}
@Get('/')
@ResponseSchema(ResponseUserPermissions)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all permissions granted to the you sorted into directly granted and inherited as permission response objects.' })
async getPermissions(@CurrentUser() currentUser: User) {
let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUserPermissions(user);
}
@Put('/')
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: "Update the yourself. <br> You can't edit your own permissions or group memberships here - Please use the /api/users/:id enpoint instead. <br> Please remember that ids can't be changed." })
async put(@CurrentUser() currentUser: User, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['groups'] });
updateUser.groups = oldUser.groups.map(g => g.id);
if (!oldUser) {
throw new UserNotFoundError();
}
if (oldUser.id != updateUser.id) {
throw new UserIdsNotMatchingError();
}
await this.userRepository.save(await updateUser.update(oldUser));
return new ResponseUser(await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Delete('/')
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
@OpenAPI({ description: 'Delete yourself. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to you they will get deleted as well.' })
async remove(@CurrentUser() currentUser: User, @QueryParam("force") force: boolean) {
if (!force) { throw new UserDeletionNotConfirmedError; }
if (!currentUser) { return UserNotFoundError; }
const responseUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
const permissionControler = new PermissionController();
for (let permission of responseUser.permissions) {
await permissionControler.remove(permission.id, true);
}
await this.userRepository.delete(currentUser);
return new ResponseUser(responseUser);
}
}

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: ['address', 'contact', 'teams'] });
runners.forEach(runner => {
responseTeams.push(new ResponseRunnerOrganisation(runner));
});
return responseTeams;
}
@Get('/:id')
@Authorized("ORGANISATION:GET")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerOrganisationNotFoundError)
@OpenAPI({ description: 'Lists all information about the organisation whose id got provided.' })
async getOne(@Param('id') id: number) {
let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] });
if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); }
return new ResponseRunnerOrganisation(runnerOrg);
}
@Post()
@Authorized("ORGANISATION:CREATE")
@ResponseSchema(ResponseRunnerOrganisation)
@OpenAPI({ description: 'Create a new organsisation.' })
async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) {
let runnerOrganisation;
try {
runnerOrganisation = await createRunnerOrganisation.toEntity();
} catch (error) {
throw error;
}
runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation);
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }));
}
@Put('/:id')
@Authorized("ORGANISATION:UPDATE")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the organisation whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) updateOrganisation: UpdateRunnerOrganisation) {
let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!oldRunnerOrganisation) {
throw new RunnerOrganisationNotFoundError();
}
if (oldRunnerOrganisation.id != updateOrganisation.id) {
throw new RunnerOrganisationIdsNotMatchingError();
}
await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] }));
}
@Delete('/:id')
@Authorized("ORGANISATION:DELETE")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organisation still has runners and/or teams associated this will fail. <br> To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> If no organisation with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!organisation) { return null; }
let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] });
if (!force) {
if (runnerOrganisation.teams.length != 0) {
throw new RunnerOrganisationHasTeamsError();
}
}
const teamController = new RunnerTeamController()
for (let team of runnerOrganisation.teams) {
await teamController.remove(team.id, true);
}
if (!force) {
if (runnerOrganisation.runners.length != 0) {
throw new RunnerOrganisationHasRunnersError();
}
}
const runnerController = new RunnerController()
for (let runner of runnerOrganisation.runners) {
await runnerController.remove(runner.id, true);
}
const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation);
await this.runnerOrganisationRepository.delete(organisation);
return responseOrganisation;
}
}

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)
@@ -93,7 +108,7 @@ export class RunnerTeamController {
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> If no team with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact.<br> If no team with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let team = await this.runnerTeamRepository.findOne({ id: id });
if (!team) { return null; }

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

View File

@@ -1,7 +1,7 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUser } from '../models/actions/create/CreateUser';
import { UpdateUser } from '../models/actions/update/UpdateUser';
@@ -105,9 +105,11 @@ export class UserController {
@Authorized("USER:DELETE")
@ResponseSchema(ResponseUser)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the user whose id you provided. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
@OpenAPI({ description: 'Delete the user whose id you provided. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
if (!force) { throw new UserDeletionNotConfirmedError; }
let user = await this.userRepository.findOne({ id: id });
if (!user) { return null; }
const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;

View File

@@ -1,24 +1,57 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
import { BadRequestError } from 'routing-controllers';
/**
* Error to throw, when to provided address doesn't belong to the accepted types.
* Error to throw when an address's postal code fails validation.
*/
export class AddressWrongTypeError extends NotAcceptableError {
export class AddressPostalCodeInvalidError extends BadRequestError {
@IsString()
name = "AddressWrongTypeError"
name = "AddressPostalCodeInvalidError"
@IsString()
message = "The address must be an existing address's id. \n You provided a object of another type."
message = "The postal code you provided is invalid. \n Please check if your postal code follows the postal code validation guidelines."
}
/**
* Error to throw, when a non-existent address get's loaded.
* Error to throw when an non-empty address's first line isn't set.
*/
export class AddressNotFoundError extends NotFoundError {
export class AddressFirstLineEmptyError extends BadRequestError {
@IsString()
name = "AddressNotFoundError"
name = "AddressFirstLineEmptyError"
@IsString()
message = "The address you provided couldn't be located in the system. \n Please check your request."
message = "You provided a empty first address line. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's postal code isn't set.
*/
export class AddressPostalCodeEmptyError extends BadRequestError {
@IsString()
name = "AddressPostalCodeEmptyError"
@IsString()
message = "You provided a empty postal code. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's city isn't set.
*/
export class AddressCityEmptyError extends BadRequestError {
@IsString()
name = "AddressCityEmptyError"
@IsString()
message = "You provided a empty city. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}
/**
* Error to throw when an non-empty address's country isn't set.
*/
export class AddressCountryEmptyError extends BadRequestError {
@IsString()
name = "AddressCountryEmptyError"
@IsString()
message = "You provided a empty country. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
}

View File

@@ -2,18 +2,7 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw, when a provided groupContact doesn't belong to the accepted types.
*/
export class GroupContactWrongTypeError extends NotAcceptableError {
@IsString()
name = "GroupContactWrongTypeError"
@IsString()
message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type."
}
/**
* Error to throw, when a non-existent groupContact get's loaded.
* Error to throw, when a non-existent contact get's requested.
*/
export class GroupContactNotFoundError extends NotFoundError {
@IsString()
@@ -21,4 +10,16 @@ export class GroupContactNotFoundError extends NotFoundError {
@IsString()
message = "The groupContact you provided couldn't be located in the system. \n Please check your request."
}
}
/**
* Error to throw when two contacts' ids don't match.
* Usually occurs when a user tries to change a contact's id.
*/
export class GroupContactIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "GroupContactIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a contact's id: This isn't allowed!"
}

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

@@ -59,4 +59,16 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
@IsString()
message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
}
/**
* Error to throw when two users' ids don't match.
* Usually occurs when a user tries to change a user's id.
*/
export class UserDeletionNotConfirmedError extends NotAcceptableError {
@IsString()
name = "UserDeletionNotConfirmedError"
@IsString()
message = "You are trying to delete a user! \n If you're sure about doing this: provide the ?force=true query param."
}

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/${token}`
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

@@ -0,0 +1,58 @@
import cookie from "cookie";
import * as jwt from "jsonwebtoken";
import { Action } from 'routing-controllers';
import { getConnectionManager } from 'typeorm';
import { config } from '../config';
import { IllegalJWTError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from '../errors/AuthError';
import { JwtCreator, JwtUser } from '../jwtcreator';
import { User } from '../models/entities/User';
/**
* TODO:
*/
const UserChecker = async (action: Action) => {
let jwtPayload = undefined
try {
let provided_token = "" + action.request.headers["authorization"].replace("Bearer ", "");
jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret);
jwtPayload = jwtPayload["userdetails"];
} catch (error) {
jwtPayload = await refresh(action);
}
const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] })
if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
if (user.enabled == false) { throw new UserDisabledError(); }
return user;
};
/**
* Handles soft-refreshing of access-tokens.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
*/
const refresh = async (action: Action) => {
let refresh_token = undefined;
try {
refresh_token = cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"];
}
catch {
throw new IllegalJWTError();
}
let jwtPayload = undefined;
try {
jwtPayload = <any>jwt.verify(refresh_token, config.jwt_secret);
} catch (error) {
throw new IllegalJWTError();
}
const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions', 'groups', 'groups.permissions'] })
if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
if (user.enabled == false) { throw new UserDisabledError(); }
let newAccess = JwtCreator.createAccess(user);
action.response.header("authorization", "Bearer " + newAccess);
return await new JwtUser(user);
}
export default UserChecker;

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,69 +0,0 @@
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
import { config } from '../../../config';
import { Address } from '../../entities/Address';
/**
* This classed is used to create a new Address entity from a json body (post request).
*/
export class CreateAddress {
/**
* The newaddress's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* The new address's first line.
* Containing the street and house number.
*/
@IsString()
@IsNotEmpty()
address1: string;
/**
* The new address's second line.
* Containing optional information.
*/
@IsString()
@IsOptional()
address2?: string;
/**
* The new address's postal code.
* This will get checked against the postal code syntax for the configured country.
*/
@IsString()
@IsNotEmpty()
@IsPostalCode(config.postalcode_validation_countrycode)
postalcode: string;
/**
* The new address's city.
*/
@IsString()
@IsNotEmpty()
city: string;
/**
* The new address's country.
*/
@IsString()
@IsNotEmpty()
country: string;
/**
* Creates a new Address entity from this.
*/
public async toEntity(): Promise<Address> {
let newAddress: Address = new Address();
newAddress.address1 = this.address1;
newAddress.address2 = this.address2;
newAddress.postalcode = this.postalcode;
newAddress.city = this.city;
newAddress.country = this.country;
return newAddress;
}
}

View File

@@ -1,5 +1,6 @@
import { IsBoolean, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
import { Address } from '../../entities/Address';
import { Donor } from '../../entities/Donor';
import { CreateParticipant } from './CreateParticipant';
@@ -26,10 +27,10 @@ export class CreateDonor extends CreateParticipant {
newDonor.lastname = this.lastname;
newDonor.phone = this.phone;
newDonor.email = this.email;
newDonor.address = await this.getAddress();
newDonor.receiptNeeded = this.receiptNeeded;
if (this.receiptNeeded == true && this.address == null) {
newDonor.address = this.address;
Address.validate(newDonor.address);
if (this.receiptNeeded == true && Address.isValidAddress(newDonor.address) == false) {
throw new DonorReceiptAddressNeededError()
}

View File

@@ -1,12 +1,13 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact';
import { RunnerGroup } from '../../entities/RunnerGroup';
/**
* This classed is used to create a new Group entity from a json body (post request).
* This classed is used to create a new GroupContact entity from a json body (post request).
*/
export class CreateGroupContact {
/**
@@ -31,11 +32,11 @@ export class CreateGroupContact {
lastname: string;
/**
* The new contact's address's id.
* The new contact's address.
*/
@IsInt()
@IsOptional()
address?: number;
@IsObject()
address?: Address;
/**
* The contact's phone number.
@@ -46,33 +47,51 @@ export class CreateGroupContact {
phone?: string;
/**
* The contact's email address.
* The new contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* Gets the new contact's address by it's id.
* The new contacts's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
@IsOptional()
groups?: number[] | number
/**
* Get's all groups for this contact by their id's;
*/
public async getGroups(): Promise<RunnerGroup[]> {
if (!this.groups) { return null; }
let groups = new Array<RunnerGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
if (!found) { throw new RunnerGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
/**
* Creates a new Address entity from this.
* Creates a new GroupContact entity from this.
*/
public async toEntity(): Promise<GroupContact> {
let contact: GroupContact = new GroupContact();
contact.firstname = this.firstname;
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.email = this.email;
contact.phone = this.phone;
contact.address = await this.getAddress();
return null;
let newContact: GroupContact = new GroupContact();
newContact.firstname = this.firstname;
newContact.middlename = this.middlename;
newContact.lastname = this.lastname;
newContact.email = this.email;
newContact.phone = this.phone;
newContact.address = this.address;
Address.validate(newContact.address);
newContact.groups = await this.getGroups();
return newContact;
}
}

View File

@@ -1,7 +1,5 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { config } from '../../../config';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { Address } from '../../entities/Address';
/**
@@ -47,19 +45,9 @@ export abstract class CreateParticipant {
email?: string;
/**
* The new participant's address's id.
* The new participant's address.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Gets the new participant's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
@IsObject()
address?: Address;
}

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

View File

@@ -1,8 +1,9 @@
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';
import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
@@ -30,7 +31,8 @@ export class CreateRunner extends CreateParticipant {
newRunner.phone = this.phone;
newRunner.email = this.email;
newRunner.group = await this.getGroup();
newRunner.address = await this.getAddress();
newRunner.address = this.address;
Address.validate(newRunner.address);
return newRunner;
}
@@ -48,6 +50,6 @@ export class CreateRunner extends CreateParticipant {
return group;
}
throw new RunnerOrganisationWrongTypeError;
throw new RunnerOrganizationWrongTypeError;
}
}

View File

@@ -1,41 +0,0 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { Address } from '../../entities/Address';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This 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's id.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Gets the org's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return 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 = await this.getAddress();
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,5 +1,6 @@
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
import { Address } from '../../entities/Address';
import { Donor } from '../../entities/Donor';
import { CreateParticipant } from '../create/CreateParticipant';
@@ -33,9 +34,10 @@ export class UpdateDonor extends CreateParticipant {
donor.phone = this.phone;
donor.email = this.email;
donor.receiptNeeded = this.receiptNeeded;
donor.address = await this.getAddress();
if (this.receiptNeeded == true && this.address == null) {
if (!this.address) { donor.address.reset(); }
else { donor.address = this.address; }
Address.validate(donor.address);
if (this.receiptNeeded == true && Address.isValidAddress(donor.address) == false) {
throw new DonorReceiptAddressNeededError()
}

View File

@@ -0,0 +1,106 @@
import { IsEmail, IsInt, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact';
import { RunnerGroup } from '../../entities/RunnerGroup';
/**
* This class is used to update a GroupContact entity (via put request).
*/
export class UpdateGroupContact {
/**
* The updated contact's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The updated contact's middle name.
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The updated contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The updated contact's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* The updated contact's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The updated contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* The updated contacts's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* Get's all groups for this contact by their id's;
*/
public async getGroups(): Promise<RunnerGroup[]> {
if (!this.groups) { return null; }
let groups = new Array<RunnerGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
if (!found) { throw new RunnerGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
/**
* Updates a provided Donor entity based on this.
* @param contact the contact you want to update.
*/
public async update(contact: GroupContact): Promise<GroupContact> {
contact.firstname = this.firstname; GroupContact
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.phone = this.phone;
contact.email = this.email;
if (!this.address) { contact.address.reset(); }
else { contact.address = this.address; }
Address.validate(contact.address);
contact.groups = await this.getGroups();
return contact;
}
}

View File

@@ -2,6 +2,7 @@ import { IsInt, IsPositive } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner';
import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from '../create/CreateParticipant';
@@ -35,7 +36,9 @@ export class UpdateRunner extends CreateParticipant {
runner.phone = this.phone;
runner.email = this.email;
runner.group = await this.getGroup();
runner.address = await this.getAddress();
if (!this.address) { runner.address.reset(); }
else { runner.address = this.address; }
Address.validate(runner.address);
return runner;
}

View File

@@ -1,48 +0,0 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError } from '../../../errors/AddressErrors';
import { Address } from '../../entities/Address';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from '../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's id.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Loads the organisation's address based on it's id.
*/
public async getAddress(): Promise<Address> {
if (!this.address) { return null; }
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return 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();
organisation.address = await this.getAddress();
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

@@ -76,6 +76,7 @@ export class UpdateUser {
* Should the user be enabled?
*/
@IsBoolean()
@IsOptional()
enabled: boolean = true;
/**

View File

@@ -1,44 +1,24 @@
import {
IsInt,
IsNotEmpty,
IsOptional,
IsPostalCode,
IsString
} from "class-validator";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { Column } from "typeorm";
import ValidatorJS from 'validator';
import { config } from '../../config';
import { IAddressUser } from './IAddressUser';
import { AddressCityEmptyError, AddressCountryEmptyError, AddressFirstLineEmptyError, AddressPostalCodeEmptyError, AddressPostalCodeInvalidError } from '../../errors/AddressErrors';
/**
* Defines the Address entity.
* Defines the Address class.
* Implemented this way to prevent any formatting differences.
*/
@Entity()
export class Address {
/**
* Autogenerated unique id (primary key).
*/
@PrimaryGeneratedColumn()
@IsInt()
id: number;
/**
* The address's description.
* Optional and mostly for UX.
*/
@Column({ nullable: true })
@IsString()
@IsOptional()
description?: string;
/**
* The address's first line.
* Containing the street and house number.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
address1: string;
address1?: string;
/**
* The address's second line.
@@ -46,45 +26,61 @@ export class Address {
*/
@Column({ nullable: true })
@IsString()
@IsOptional()
address2?: string;
/**
* The address's postal code.
* This will get checked against the postal code syntax for the configured country.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
@IsPostalCode(config.postalcode_validation_countrycode)
postalcode: string;
/**
* The address's city.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
city: string;
/**
* The address's country.
*/
@Column()
@Column({ nullable: true })
@IsString()
@IsNotEmpty()
country: string;
/**
* Used to link the address to participants.
*/
@OneToMany(() => IAddressUser, addressUser => addressUser.address, { nullable: true })
addressUsers: IAddressUser[];
public reset() {
this.address1 = null;
this.address2 = null;
this.city = null;
this.country = null;
this.postalcode = null;
}
/**
* Turns this entity into it's response class.
* Checks if this is a valid address
*/
public toResponse() {
return new Error("NotImplemented");
public static isValidAddress(address: Address): Boolean {
if (address == null) { return false; }
if (address.address1 == null || address.city == null || address.country == null || address.postalcode == null) { return false; }
if (ValidatorJS.isPostalCode(address.postalcode, config.postalcode_validation_countrycode) == false) { return false; }
return true;
}
/**
* This function validates addresses.
* This is a workaround for non-existant class validation for embedded entities.
* @param address The address that shall get validated.
*/
public static validate(address: Address) {
if (address == null) { return; }
if (address.address1 == null && address.city == null && address.country == null && address.postalcode == null) { return; }
if (address.address1 == null) { throw new AddressFirstLineEmptyError(); }
if (address.postalcode == null) { throw new AddressPostalCodeEmptyError(); }
if (address.city == null) { throw new AddressCityEmptyError(); }
if (address.country == null) { throw new AddressCountryEmptyError(); }
if (ValidatorJS.isPostalCode(address.postalcode.toString(), config.postalcode_validation_countrycode) == false) { throw new AddressPostalCodeInvalidError(); }
}
}

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

@@ -7,10 +7,10 @@ import {
IsString
} from "class-validator";
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { config } from '../../config';
import { ResponseGroupContact } from '../responses/ResponseGroupContact';
import { Address } from "./Address";
import { IAddressUser } from './IAddressUser';
import { RunnerGroup } from "./RunnerGroup";
/**
@@ -18,7 +18,7 @@ import { RunnerGroup } from "./RunnerGroup";
* Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
*/
@Entity()
export class GroupContact implements IAddressUser {
export class GroupContact {
/**
* Autogenerated unique id (primary key).
*/
@@ -54,8 +54,7 @@ export class GroupContact implements IAddressUser {
* The contact's address.
* This is a address object to prevent any formatting differences.
*/
@IsOptional()
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
@Column(type => Address)
address?: Address;
/**
@@ -85,7 +84,7 @@ export class GroupContact implements IAddressUser {
/**
* Turns this entity into it's response class.
*/
public toResponse() {
return new Error("NotImplemented");
public toResponse(): ResponseGroupContact {
return new ResponseGroupContact(this);
}
}

View File

@@ -1,20 +0,0 @@
import { Entity, ManyToOne, PrimaryColumn } from 'typeorm';
import { Address } from './Address';
/**
* The interface(tm) all entities using addresses have to implement.
* This is a abstract class, because apparently typeorm can't really work with interfaces :/
*/
@Entity()
export abstract class IAddressUser {
@PrimaryColumn()
id: number;
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
address?: Address
/**
* Turns this entity into it's response class.
*/
public abstract toResponse();
}

View File

@@ -7,11 +7,10 @@ import {
IsString
} from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { Column, Entity, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { config } from '../../config';
import { ResponseParticipant } from '../responses/ResponseParticipant';
import { Address } from "./Address";
import { IAddressUser } from './IAddressUser';
/**
* Defines the Participant entity.
@@ -19,7 +18,7 @@ import { IAddressUser } from './IAddressUser';
*/
@Entity()
@TableInheritance({ column: { name: "type", type: "varchar" } })
export abstract class Participant implements IAddressUser {
export abstract class Participant {
/**
* Autogenerated unique id (primary key).
*/
@@ -55,7 +54,7 @@ export abstract class Participant implements IAddressUser {
* The participant's address.
* This is a address object to prevent any formatting differences.
*/
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
@Column(type => Address)
address?: Address;
/**

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,35 +1,44 @@
import { IsInt, IsOptional } from "class-validator";
import { ChildEntity, ManyToOne, OneToMany } from "typeorm";
import { ResponseRunnerOrganisation } from '../responses/ResponseRunnerOrganisation';
import { IsInt, IsOptional, IsString } from "class-validator";
import { ChildEntity, Column, OneToMany } from "typeorm";
import { ResponseRunnerOrganization } from '../responses/ResponseRunnerOrganization';
import { Address } from './Address';
import { IAddressUser } from './IAddressUser';
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 implements IAddressUser {
export class RunnerOrganization extends RunnerGroup {
/**
* The organisations's address.
* The organizations's address.
*/
@IsOptional()
@ManyToOne(() => Address, address => address.addressUsers, { nullable: true })
@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>();
@@ -59,7 +68,7 @@ export class RunnerOrganisation extends RunnerGroup implements IAddressUser {
/**
* 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

@@ -138,8 +138,10 @@ export class User extends Principal {
if (!this.groups) { return returnPermissions; }
for (let group of this.groups) {
for (let permission of group.permissions) {
returnPermissions.push(permission);
if (group.permissions) {
for (let permission of group.permissions) {
returnPermissions.push(permission);
}
}
}
return returnPermissions;
@@ -159,8 +161,10 @@ export class User extends Principal {
if (!this.groups) { return returnPermissions; }
for (let group of this.groups) {
for (let permission of group.permissions) {
returnPermissions.push(permission.toString());
if (group.permissions) {
for (let permission of group.permissions) {
returnPermissions.push(permission.toString());
}
}
}
return Array.from(new Set(returnPermissions));

View File

@@ -1,40 +1,39 @@
import {
IsNotEmpty,
IsOptional,
IsString
} from "class-validator";
import { ChildEntity, Column } from "typeorm";
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { ResponseUserGroup } from '../responses/ResponseUserGroup';
import { Principal } from './Principal';
/**
* Defines the UserGroup entity.
* This entity describes a group of users with a set of permissions.
*/
@ChildEntity()
export class UserGroup extends Principal {
/**
* The group's name
*/
@Column()
@IsNotEmpty()
@IsString()
name: string;
/**
* The group's description
*/
@Column({ nullable: true })
@IsOptional()
@IsString()
description?: string;
/**
* Turns this entity into it's response class.
*/
public toResponse(): ResponsePrincipal {
return new ResponseUserGroup(this);
}
import {
IsNotEmpty,
IsOptional,
IsString
} from "class-validator";
import { ChildEntity, Column } from "typeorm";
import { ResponseUserGroup } from '../responses/ResponseUserGroup';
import { Principal } from './Principal';
/**
* Defines the UserGroup entity.
* This entity describes a group of users with a set of permissions.
*/
@ChildEntity()
export class UserGroup extends Principal {
/**
* The group's name
*/
@Column()
@IsNotEmpty()
@IsString()
name: string;
/**
* The group's description
*/
@Column({ nullable: true })
@IsOptional()
@IsString()
description?: string;
/**
* Turns this entity into it's response class.
*/
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',
@@ -14,5 +14,7 @@ export enum PermissionTarget {
SCAN = 'SCAN',
STATION = 'STATION',
CARD = 'CARD',
DONATION = 'DONATION'
DONATION = 'DONATION',
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

@@ -0,0 +1,86 @@
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 implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.GROUPCONTACT;
/**
* The contact's id.
*/
@IsInt()
id: number;
/**
* The contact's first name.
*/
@IsString()
firstname: string;
/**
* The contact's middle name.
*/
@IsString()
middlename?: string;
/**
* The contact's last name.
*/
@IsString()
lastname: string;
/**
* The contact's phone number.
*/
@IsString()
phone?: string;
/**
* The contact's e-mail address.
*/
@IsString()
email?: string;
/**
* The contact's associated runner groups.
*/
@IsObject()
groups: ResponseRunnerGroup[];
/**
* The contact's address.
* This is a address object to prevent any formatting differences.
*/
@IsObject()
address?: Address;
/**
* Creates a ResponseGroupContact object from a contact.
* @param contact The contact the response shall be build for.
*/
public constructor(contact: GroupContact) {
this.id = contact.id;
this.firstname = contact.firstname;
this.middlename = contact.middlename;
this.lastname = contact.lastname;
this.phone = contact.phone;
this.email = contact.email;
this.address = contact.address;
this.groups = new Array<ResponseRunnerGroup>();
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,10 +1,19 @@
import { IsInt, IsString } from "class-validator";
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.
*/
@@ -41,6 +50,13 @@ export abstract class ResponseParticipant {
@IsString()
email?: string;
/**
* The participant's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* Creates a ResponseParticipant object from a participant.
* @param participant The participant the response shall be build for.
@@ -52,5 +68,6 @@ export abstract class ResponseParticipant {
this.lastname = participant.lastname;
this.phone = participant.phone;
this.email = participant.email;
this.address = participant.address;
}
}

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

@@ -1,97 +1,110 @@
import {
IsArray,
IsBoolean,
IsOptional,
IsString
} from "class-validator";
import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup';
import { ResponsePrincipal } from './ResponsePrincipal';
/**
* Defines the user response.
*/
export class ResponseUser extends ResponsePrincipal {
/**
* The user's first name.
*/
@IsString()
firstname: string;
/**
* The user's middle name.
*/
@IsString()
middlename?: string;
/**
* The user's last name.
*/
@IsString()
lastname: string;
/**
* The user's phone number.
*/
@IsString()
phone?: string;
/**
* The user's e-mail address.
*/
@IsString()
email?: string;
/**
* The user's username.
*/
@IsString()
username?: string;
/**
* Is user enabled?
*/
@IsBoolean()
enabled: boolean = true;
/**
* The user's profile pic (or rather a url pointing to it).
*/
@IsString()
profilePic: string;
/**
* The groups that the user is a part of.
*/
@IsArray()
@IsOptional()
groups: UserGroup[];
/**
* The user's permissions.
* Directly granted or inherited converted to their string form and deduplicated.
*/
@IsArray()
@IsOptional()
permissions: string[];
/**
* Creates a ResponseUser object from a user.
* @param user The user the response shall be build for.
*/
public constructor(user: User) {
super(user);
this.firstname = user.firstname;
this.middlename = user.middlename;
this.lastname = user.lastname;
this.phone = user.phone;
this.email = user.email;
this.username = user.username;
this.enabled = user.enabled;
this.profilePic = user.profilePic;
this.groups = user.groups;
this.permissions = user.allPermissions;
this.groups.forEach(function (g) { delete g.permissions });
}
}
import {
IsArray,
IsBoolean,
IsOptional,
IsString
} from "class-validator";
import { User } from '../entities/User';
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 implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.USER;
/**
* The user's first name.
*/
@IsString()
firstname: string;
/**
* The user's middle name.
*/
@IsString()
middlename?: string;
/**
* The user's last name.
*/
@IsString()
lastname: string;
/**
* The user's phone number.
*/
@IsString()
phone?: string;
/**
* The user's e-mail address.
*/
@IsString()
email?: string;
/**
* The user's username.
*/
@IsString()
username?: string;
/**
* Is user enabled?
*/
@IsBoolean()
enabled: boolean = true;
/**
* The user's profile pic (or rather a url pointing to it).
*/
@IsString()
profilePic: string;
/**
* The groups that the user is a part of.
*/
@IsArray()
@IsOptional()
groups: ResponseUserGroup[];
/**
* The user's permissions.
* Directly granted or inherited converted to their string form and deduplicated.
*/
@IsArray()
@IsOptional()
permissions: string[];
/**
* Creates a ResponseUser object from a user.
* @param user The user the response shall be build for.
*/
public constructor(user: User) {
super(user);
this.firstname = user.firstname;
this.middlename = user.middlename;
this.lastname = user.lastname;
this.phone = user.phone;
this.email = user.email;
this.username = user.username;
this.enabled = user.enabled;
this.profilePic = user.profilePic;
this.groups = new Array<ResponseUserGroup>();
this.permissions = user.allPermissions;
if (user.groups) {
for (let group of user.groups) {
delete group.permissions;
this.groups.push(group.toResponse());
}
}
}
}

View File

@@ -1,41 +1,53 @@
import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { Permission } from '../entities/Permission';
import { UserGroup } from '../entities/UserGroup';
import { ResponsePrincipal } from './ResponsePrincipal';
/**
* Defines the userGroup response.
*/
export class ResponseUserGroup extends ResponsePrincipal {
/**
* The userGroup's name.
*/
@IsNotEmpty()
@IsString()
name: string;
/**
* The userGroup's description.
*/
@IsOptional()
@IsString()
description?: string;
/**
* The userGroup's permissions.
*/
@IsArray()
@IsOptional()
permissions: Permission[];
/**
* Creates a ResponseUserGroup object from a userGroup.
* @param group The userGroup the response shall be build for.
*/
public constructor(group: UserGroup) {
super(group);
this.name = group.name;
this.description = group.description;
this.permissions = group.permissions;
}
}
import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator";
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 implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.USERGROUP;
/**
* The userGroup's name.
*/
@IsNotEmpty()
@IsString()
name: string;
/**
* The userGroup's description.
*/
@IsOptional()
@IsString()
description?: string;
/**
* The userGroup's permissions.
*/
@IsArray()
@IsOptional()
permissions: ResponsePermission[];
/**
* Creates a ResponseUserGroup object from a userGroup.
* @param group The userGroup the response shall be build for.
*/
public constructor(group: UserGroup) {
super(group);
this.name = group.name;
this.description = group.description;
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:搆Ǩ>ÙՠƳ|"
]
}

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