Compare commits

...

615 Commits

Author SHA1 Message Date
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
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 failed
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
38b9a772cd Merge pull request 'First feature version 0.1.0' (#102) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #102
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-15 19:31:40 +00:00
618430433d 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-15 19:18:43 +00:00
84cd398c09 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-15 20:18:19 +01:00
385a9bba73 Fixed broken pkg stuff
ref #102
2021-01-15 20:18:14 +01:00
8218a452bd 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-15 18:18:39 +00:00
a77e2eb3ad Fixed country code type issue
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
https://ci.odit.services/lfk/backend/252/1/2 ref #102
2021-01-15 19:18:23 +01:00
d1a0bed00e 🧾New changelog file version [CI SKIP] [skip ci]
Some checks failed
continuous-integration/drone/pr Build is failing
2021-01-15 18:02:24 +00:00
66d4770858 Merge branch 'dev' of git.odit.services:lfk/backend into dev
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2021-01-15 19:01:32 +01:00
80c5f9b84d 🚀Bumped version to v0.1.0 2021-01-15 19:01:05 +01:00
79f46cb745 🧾New changelog file version [CI SKIP] [skip ci]
Some checks failed
continuous-integration/drone/pr Build is failing
2021-01-15 18:01:01 +00:00
de32a9862d 👊 Bumped dependency
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
ref #102
2021-01-15 19:00:45 +01:00
0e119e4834 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-15 17:58:01 +00:00
29c8e00477 Merge pull request 'Switched to accepting ids (numbers/number arrays) feature/90-accept_objects' (#101) from feature/90-accept_objects into dev
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is passing
Reviewed-on: #101
2021-01-15 17:57:45 +00:00
dc6ad9cdd3 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-15 17:53:56 +00:00
dcd754dac8 Merge branch 'dev' into feature/90-accept_objects
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-15 18:53:52 +01:00
d88fb18319 Switched tests over to the new id-only schema
All checks were successful
continuous-integration/drone/pr Build is passing
ref #90
2021-01-15 18:50:35 +01:00
420e9c4662 Updated faulty getter function
ref #90
2021-01-15 18:39:30 +01:00
98d6a1cc64 Fixed old reference
ref #90
2021-01-15 18:39:04 +01:00
09ad081b37 Updated faulty getter function
ref #90
2021-01-15 18:36:57 +01:00
aa0fd9cafd Refactoring: switched update user groups from objects to ids
ref #90
2021-01-15 18:35:21 +01:00
bae8290273 Switched to full update from partial and resolved relation
ref #90
2021-01-15 18:33:53 +01:00
1b799a6973 Clarified comments
ref #90
2021-01-15 18:32:41 +01:00
ed3b55a1e2 Refactoring: switched update team parent from objects to ids
ref #90
2021-01-15 18:31:23 +01:00
97c01ce81a Refactoring: switched update org address from objects to ids
ref #90
2021-01-15 18:30:20 +01:00
e96637219f Refactoring: switched update runner group from objects to ids
ref #90
2021-01-15 18:29:30 +01:00
17244b0006 Clarified comments
ref #90
2021-01-15 18:28:24 +01:00
67a02f06da Merge branch 'feature/90-accept_objects' of git.odit.services:lfk/backend into feature/90-accept_objects
# Conflicts:
#	src/models/actions/update/UpdatePermission.ts
2021-01-15 18:27:32 +01:00
6b6f345618 Refactoring: switched from objects to ids
ref #90
2021-01-15 18:27:21 +01:00
2ac9d3e977 Refactoring: switched from objects to ids
ref #90
2021-01-15 18:26:39 +01:00
93692ec255 Clarified comments
ref #90
2021-01-15 18:25:48 +01:00
99852f591e Clarified comments
ref #90
2021-01-15 18:23:30 +01:00
b89525746d Clarified comments
ref #90
2021-01-15 18:22:26 +01:00
c05834f2a1 Removed useless parts from functions and updated comments
ref #90
2021-01-15 18:20:56 +01:00
9bbfb4763d Clarified comments
ref #90
2021-01-15 18:19:34 +01:00
22e6070e53 Removed useless part from function and updated comments
ref #90
2021-01-15 18:19:26 +01:00
ba218c85e0 Made addresses optional gain 2021-01-15 18:18:26 +01:00
644d2b06ac Removed useless part from function and updated comments
ref #90
2021-01-15 18:13:53 +01:00
8d4c8a4553 Removed useless part from function
ref #90
2021-01-15 18:13:10 +01:00
077174a9a2 Clarified comments
ref #90
2021-01-15 18:12:55 +01:00
ce31b95fb7 Removed todo
#90
2021-01-15 18:10:55 +01:00
881eedbf3a Merge pull request 'Alpha Release 0.0.12' (#98) from dev into main
Some checks failed
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #98
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-13 19:33:07 +00:00
09cb6f7b2b 🧾New changelog file version [CI SKIP] [skip ci]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-13 18:25:55 +00:00
bd091d5cb9 🚀Bumped version to v0.0.12
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-13 19:25:28 +01:00
8cb67a8d20 🧾New changelog file version [CI SKIP] [skip ci] 2021-01-13 18:22:48 +00:00
290bb29e64 Disabled auto clone
All checks were successful
continuous-integration/drone/push Build is passing
ref #63
2021-01-13 19:22:19 +01:00
d0769a5e37 Added secondary full clone for tags
Some checks failed
continuous-integration/drone/push Build is failing
ref #63
2021-01-13 19:20:42 +01:00
c5b28df2ae Merge branch 'dev' of git.odit.services:lfk/backend into dev
All checks were successful
continuous-integration/drone/push Build is passing
# Conflicts:
#	.drone.yml
2021-01-13 19:18:39 +01:00
c108fa509f Updated step order
ref #63
2021-01-13 19:18:31 +01:00
1e5e9801be Updated step order
ref #63
2021-01-13 19:18:08 +01:00
09b16c980b 📖New license file version [CI SKIP] [skip ci] 2021-01-13 18:15:22 +00:00
4c26fc808e Fixed spellings
All checks were successful
continuous-integration/drone/push Build is passing
ref #63
2021-01-13 19:14:16 +01:00
525b11b346 Revert "🚀Bumped version to v0.0.12."
Some checks failed
continuous-integration/drone/push Build encountered an error
This reverts commit 86679b498b.
2021-01-13 19:13:04 +01:00
86679b498b 🚀Bumped version to v0.0.12. 2021-01-13 19:12:50 +01:00
46df8b0528 Updated the release machanics
ref #63
2021-01-13 19:12:43 +01:00
1a4f896a8a Merge branch 'dev' of git.odit.services:lfk/backend into dev 2021-01-13 19:07:57 +01:00
aaaa15a0ef Moved changelog generation to dev build for now
ref #63
2021-01-13 19:07:50 +01:00
de65b1c699 🧾New changelog file version [CI SKIP] [skip ci]
Some checks failed
continuous-integration/drone/pr Build was killed
2021-01-13 17:58:33 +00:00
f9437065ee 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-13 18:57:17 +01:00
b495cadae9 Added new ci skipping flags
ref #63
2021-01-13 18:57:11 +01:00
47995b77f7 🧾New changelog file version [CI SKIP]
Some checks failed
continuous-integration/drone/pr Build was killed
2021-01-13 17:55:17 +00:00
bc24ec5272 🧾New changelog file version [CI SKIP]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-13 17:54:26 +00:00
2947c41a72 🧾New changelog file version [CI SKIP]
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-13 17:54:00 +00:00
ef53035f70 Reenabled dev build
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
ref #63
2021-01-13 18:53:38 +01:00
290afc3f8f Disabled verification skip
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
ref #63
2021-01-13 18:50:50 +01:00
d6e89b0880 Merge branch 'dev' of git.odit.services:lfk/backend into dev
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2021-01-13 18:47:03 +01:00
2b72552b1f tmp: skip verification 2021-01-13 18:47:00 +01:00
df69418855 tmp: skip verification
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
2021-01-13 18:45:04 +01:00
472e402521 disabled dev build temporary
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
ref #63
2021-01-13 18:42:33 +01:00
a3f282667c Merge branch 'dev' of git.odit.services:lfk/backend into dev
# Conflicts:
#	.drone.yml
2021-01-13 18:42:01 +01:00
b86263d972 Disabled custom clone
ref #63
2021-01-13 18:41:02 +01:00
f278320b93 Disabled custom clone
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
ref #63
2021-01-13 18:38:05 +01:00
6345666ae6 Added new pipeline to automagicly generate changelogs on pr to main
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is failing
ref #63
2021-01-13 18:35:12 +01:00
7b5ebab453 Merge pull request 'New user features feature/93-user_endpoints' (#95) from feature/93-user_endpoints into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #95
2021-01-13 17:30:25 +00:00
d4d713b12d Merge branch 'dev' into feature/93-user_endpoints
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-13 17:21:22 +00:00
ab3af54e15 Merge pull request 'Donation API Endpoint feature/66-donation_api' (#94) from feature/66-donation_api into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #94
2021-01-13 17:20:08 +00:00
b01e1eb8a1 Added a new endpoint that returns a users permissions as objects sorted into two arrays
All checks were successful
continuous-integration/drone/pr Build is passing
ref #93
2021-01-13 18:19:59 +01:00
0724932152 Updated some openapi descriptions
All checks were successful
continuous-integration/drone/pr Build is passing
ref #94
2021-01-13 18:01:53 +01:00
cd7b15aadf First part of resolving user inherited permissions
ref #93
2021-01-13 17:57:42 +01:00
37fc167002 Added '@' as a illegal character for usernames
ref #93
2021-01-13 17:51:42 +01:00
9feeb302e8 Switched emails to being mandetory for users
ref #93
2021-01-13 17:44:22 +01:00
bba35d189e Added donor donation amount to the donor response
Some checks failed
continuous-integration/drone/pr Build is failing
ref #66
2021-01-13 17:32:10 +01:00
cd5e4bbd60 Added donation update validtests
ref #66
2021-01-13 17:19:57 +01:00
a513bf13ca Added donation update invalid tests
ref #66
2021-01-12 20:43:07 +01:00
e3e570e664 Added donation add validtests
ref #66
2021-01-12 20:15:51 +01:00
badff85e28 Fixed typos
ref #66
2021-01-12 20:14:23 +01:00
4a0f75044f Added donation add invalid tests
ref #66
2021-01-12 20:09:00 +01:00
b729a7cead Added cascading runner deletion tests
ref #66
2021-01-12 20:01:56 +01:00
4375ca92d3 Added cascading donor deletion tests
ref #66
2021-01-12 20:00:02 +01:00
71537b283f Added donation delete tests
ref #66
2021-01-12 19:53:03 +01:00
63506dac1c Added donation get tests
ref #66
2021-01-12 19:44:15 +01:00
e716fae1c5 Implmented cascading donation deletion for runners and donors
ref #66
2021-01-12 19:33:54 +01:00
f7370bc802 Implemented distance donation updateing
ref #66
2021-01-12 19:06:26 +01:00
72c3fc78b3 Added the basics for distance donation updateing
ref #66
2021-01-12 19:03:33 +01:00
110387dbd3 Merge branch 'feature/66-donation_api' of git.odit.services:lfk/backend into feature/66-donation_api
# Conflicts:
#	src/controllers/DonationController.ts
2021-01-12 19:01:14 +01:00
2820f151e8 Implemented fixed donation updateing
ref #66
2021-01-12 19:01:03 +01:00
9517df5082 Implemented fixed donation updateing
ref #66
2021-01-12 19:00:35 +01:00
56cedf0144 Fixed typo
ref #66
2021-01-12 18:55:20 +01:00
bbaee7cd4d Added the basics for fixed donation updateing
ref #66
2021-01-12 18:53:59 +01:00
8ee2bdf488 Implemented distance donation creation
ref #66
2021-01-12 18:50:55 +01:00
97ecc83fe4 Implemented fixed donation creation
ref #66
2021-01-12 18:50:47 +01:00
57f62a6087 Implemented donation deletion
ref #66
2021-01-12 18:46:02 +01:00
2e760ff461 Implemented the donation creation action models
ref #66
2021-01-12 18:39:14 +01:00
0df26cbd54 Implemented donation getting
ref #66
2021-01-12 18:29:55 +01:00
5f1ab4a2f3 Added donation errors
ref #66
2021-01-12 18:26:55 +01:00
e1ff8c03e1 Added donation permission target
ref #66
2021-01-12 18:21:52 +01:00
55f72c35a6 Implemented the distance donation response
ref #66
2021-01-12 18:20:36 +01:00
6c53701a59 Implemented the donation response
ref #66
2021-01-12 18:16:09 +01:00
02bb634257 Implemented a response donation interface
ref #66
2021-01-12 18:07:41 +01:00
5581c03f77 Added barebones donation controller
ref #66
2021-01-12 18:01:03 +01:00
cf788fe07b Merge pull request 'Fixed backend version related bugs' (#92) from bugfix/91-backend_version into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #92
closes #91
2021-01-12 16:46:37 +00:00
4bf425e1ca Merge branch 'dev' into bugfix/91-backend_version
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-12 16:46:08 +00:00
a2f4fd5d9b Introduces a very basic version getting endpoint
All checks were successful
continuous-integration/drone/pr Build is passing
ref #91
2021-01-12 17:41:42 +01:00
295a1524d8 Fixed the version getting process
ref #91
2021-01-12 17:39:40 +01:00
234154255c Merge pull request 'Bugfix: resolved missing relation' (#89) from bugfix/88-user_update into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #89
closes #88
2021-01-12 16:34:36 +00:00
7b087840ec Bugfix: resolved missing relation
All checks were successful
continuous-integration/drone/pr Build is passing
ref #88
2021-01-12 16:53:39 +01:00
16b594ebdd Merge branch 'main' into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-10 18:28:09 +01:00
67b3101fd1 Updated some trone pipeline names and messages
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-10 18:26:11 +01:00
b3ce56c605 Merge pull request 'Alpha Release 0.0.11' (#87) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #87
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-10 17:16:09 +00:00
28cefa792c Version bump
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-10 18:13:08 +01:00
0803abc168 Merge pull request 'General cleanup and optimisation feature/76-cleanup' (#86) from feature/76-cleanup into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #86
2021-01-10 17:11:31 +00:00
02ae883fa4 Removed everything comit related from the release-it config
All checks were successful
continuous-integration/drone/pr Build is passing
ref #76
2021-01-10 18:09:57 +01:00
be4050768e Moded group updateing to a updateusergroup action model
All checks were successful
continuous-integration/drone/pr Build is passing
ref #76
2021-01-10 18:01:22 +01:00
dc6ec23cb9 Implmented basic release mgnt
ref #76
2021-01-10 17:47:31 +01:00
1bb98c13d1 Dependency bump
ref #76
2021-01-10 17:29:30 +01:00
bca979bab5 Unified remove parameters
ref #76
2021-01-10 17:16:42 +01:00
e4fafd764c Cleaner implementation of the api version getter
ref #76
2021-01-10 17:14:42 +01:00
172159414b Unified the openapi generation
ref #76
2021-01-10 17:10:25 +01:00
9355138a8c App now automagicly displays the current package version as the openapi version
ref #76
2021-01-10 16:59:39 +01:00
343cd8b772 Merge branch 'feature/76-cleanup' of git.odit.services:lfk/backend into feature/76-cleanup 2021-01-10 16:57:43 +01:00
01e0d5b94d Renamed the auth response call to ResponseAuth
ref #76
2021-01-10 16:57:40 +01:00
ac00667465 Renamed the auth response call to ResponseAuth
ref #76
2021-01-10 16:54:19 +01:00
3deae2bfeb Moved all update() and toEntity action model functions to async
ref #76
2021-01-10 16:53:59 +01:00
3f7b0f6563 Renamed the update>Entity Name>() functiuons to update()
ref #76
2021-01-10 16:35:52 +01:00
e6b9d4f273 Renamed the to>Entity Name>() functiuons to toEntity()
ref #76
2021-01-10 16:31:55 +01:00
a00231dd3c Updated imports
ref #76
2021-01-10 16:23:09 +01:00
3bc172e7e0 Intruduced a new folder structure for action models
ref #76
2021-01-10 16:10:02 +01:00
ee9df21ae5 Fixed some typos in errors
ref #76
2021-01-10 16:07:37 +01:00
f96b256ad3 Fixed some typos and extended comments for the middlewares
ref #76
2021-01-10 16:03:56 +01:00
f2c50e929e Merge branch 'dev' of git.odit.services:lfk/backend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-09 19:05:00 +01:00
02e3239848 Reverted temporary logging 2021-01-09 19:04:07 +01:00
8a54b027d0 Reverted temporary logging
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-09 18:55:16 +01:00
3b11e896d4 Merge branch 'dev' of git.odit.services:lfk/backend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-09 18:50:50 +01:00
89926b2c31 Temporary: extended live logging 2021-01-09 18:50:48 +01:00
7b4e89555e Temporary: extended live logging
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-09 18:47:11 +01:00
1e37186247 Revert "Temporary: extended live logging"
This reverts commit 154c763719.
2021-01-09 18:45:44 +01:00
154c763719 Temporary: extended live logging
All checks were successful
continuous-integration/drone/push Build is passing
ref lfk/frontend#28
2021-01-09 18:08:36 +01:00
80197d5834 Merge pull request 'feature/78-trackscan' (#85) from feature/78-trackscan into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #85
2021-01-09 16:33:09 +00:00
7e95103a2d added trackscan update tests
All checks were successful
continuous-integration/drone/pr Build is passing
ref #78
2021-01-09 17:18:33 +01:00
efe1a1f543 added trackscan delete tests
ref #78
2021-01-09 16:56:57 +01:00
4fea690670 Added missing parameter fro negative-test
ref #78
2021-01-09 16:54:19 +01:00
f1dee1061d added trackscan get tests
ref #78
2021-01-09 16:49:17 +01:00
61cf0fc08d Implemented proper scan invalidation
ref #78
2021-01-09 16:47:54 +01:00
0c86e5dae1 added trackscan add tests
ref #78
2021-01-09 16:44:52 +01:00
638898fa28 Implemented trackscan updateing
ref #78
2021-01-09 16:17:50 +01:00
e7cd68e1c8 removed distance checks from tests
ref #78
2021-01-09 15:59:36 +01:00
e40e6faebd Merge branch 'feature/78-trackscan' of git.odit.services:lfk/backend into feature/78-trackscan
# Conflicts:
#	src/controllers/RunnerController.ts
2021-01-09 15:45:35 +01:00
3d07aac944 Implemented cascading scan, track and card deletion
ref #78
2021-01-09 15:45:17 +01:00
1a5493facf Implemented cascading scan, track and card deletion
ref #78
2021-01-09 15:43:52 +01:00
9013b9492c Fixed runner distance resolution
ref #78
2021-01-09 15:25:11 +01:00
188f26ad65 Fixed manual trackscan creation
ref #78
2021-01-09 14:52:08 +01:00
3ceb5a0c0f Removed total distance from tests
ref #78
2021-01-09 14:24:16 +01:00
e1ce052d3c Fixed runner total distance not getting resolved
ref #78
2021-01-09 14:23:47 +01:00
70a379edef Merge pull request 'New feature: runner cards (feature/77-runner_cards)' (#84) from feature/77-runner_cards into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #84
2021-01-09 13:01:50 +00:00
35ea3154d1 Added card update tests
All checks were successful
continuous-integration/drone/pr Build is passing
ref #77
2021-01-09 12:42:41 +01:00
ebf66821a2 Added card delete tests
ref #77
2021-01-09 12:41:59 +01:00
8463bee253 added card add tests
ref #77
2021-01-09 12:32:47 +01:00
860680d001 Implmented the EAN generation
ref #77
2021-01-09 12:24:05 +01:00
df39166279 Added card get tests
ref #77
2021-01-09 11:59:20 +01:00
32fda46f0a Implemented runner updateing
ref #77
2021-01-09 11:55:32 +01:00
36ecae7e6e Added card creation
#17
2021-01-09 11:48:13 +01:00
a5bfe4e3d5 Added card deletion + errors
ref #77
2021-01-09 11:28:59 +01:00
4faeddc3f3 Added runner card get endpoints
ref #77
2021-01-09 11:23:12 +01:00
98f7bf366f Added card permission target
ref #77
2021-01-09 11:21:52 +01:00
af3a9e5ce2 Added basic response calss for runner cards
ref #77
2021-01-09 11:15:29 +01:00
52eb7b1afe Added a barebones runnercard controller
ref #77
2021-01-09 11:10:05 +01:00
490fbd241d Merge pull request 'Alpha Release 0.0.10' (#83) from dev into main
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #83
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-08 20:15:27 +00:00
f132131156 Version bump
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-08 21:14:20 +01:00
c1e680a063 Fixed responsescheme for the user controller
Some checks failed
continuous-integration/drone/push Build is failing
2021-01-08 21:13:41 +01:00
c66b06c2c9 Merge pull request 'Alpha Release 0.0.9' (#82) from dev into main
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #82
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-08 19:52:55 +00:00
65e605cdc4 Version bump
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-08 20:49:19 +01:00
d2fdb4efd9 Merge pull request 'All users get profile pics feature/79-profile_pics' (#81) from feature/79-profile_pics into dev
Some checks failed
continuous-integration/drone/push Build is failing
Reviewed-on: #81
closes #79
2021-01-08 19:48:22 +00:00
d0deb9d647 Fixed wrong relation getting resolved
All checks were successful
continuous-integration/drone/pr Build is passing
ref #79
2021-01-08 20:40:15 +01:00
5495c90eaf Merge branch 'dev' into feature/79-profile_pics
Some checks failed
continuous-integration/drone/pr Build is failing
2021-01-08 20:19:08 +01:00
bf3ffae67c Merge pull request 'Added scan (station) apis feature/67-scan_apis' (#80) from feature/67-scan_apis into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #80
closes #67
2021-01-08 19:18:39 +00:00
aa0337ea33 Fixed getting all permissions for users
ref #79
2021-01-08 20:17:05 +01:00
4991d735bf Pinned sqlite3 to 5.0.0 as a temporary bugfix
All checks were successful
continuous-integration/drone/pr Build is passing
ref #67
2021-01-08 20:04:04 +01:00
398e61bddb Merge branch 'feature/67-scan_apis' of git.odit.services:lfk/backend into feature/67-scan_apis
Some checks failed
continuous-integration/drone/pr Build is failing
# Conflicts:
#	.drone.yml
2021-01-08 19:37:25 +01:00
e6576f4a54 Finned node version for ci
ref #67
2021-01-08 19:37:13 +01:00
c3b9e135b0 Finned node version for ci
Some checks failed
continuous-integration/drone/pr Build is failing
ref #67
2021-01-08 19:34:39 +01:00
3bd4948c43 Merge branch 'feature/79-profile_pics' of git.odit.services:lfk/backend into feature/79-profile_pics 2021-01-08 19:32:13 +01:00
f3cd1380be First part of the permission return (buggy!)
ref #79
2021-01-08 19:32:11 +01:00
a2c3dfbf85 First part of the permission return (buggy!)
ref #71
2021-01-08 19:32:04 +01:00
3c37aafe1f Added profile pics to all user related models
ref #79
2021-01-08 19:11:50 +01:00
c591c182b3 Updated comments
Some checks failed
continuous-integration/drone/pr Build is failing
ref #67
2021-01-08 18:37:33 +01:00
9cc50078d1 Merge branch 'dev' into feature/67-scan_apis 2021-01-08 18:29:33 +01:00
7728759bcd Added openapi sec scheme for the scan station auth
ref #67
2021-01-08 18:28:35 +01:00
ce8fed350e Updated OPENAPI Descriptions for the new controllers
ref #67
2021-01-08 18:25:29 +01:00
a005945e9e Added scan add tests with the station based auth
ref #67
2021-01-08 18:09:47 +01:00
cf86520fae Fixed wrong auth type being used
ref #67
2021-01-08 18:08:13 +01:00
db6fdf6baf Implemented scan auth middleware
ref #67
2021-01-08 17:50:29 +01:00
975ad50afc Added scan update tests
ref #67
2021-01-08 17:42:05 +01:00
0c27df7754 Added scan add tests
ref #67
2021-01-08 17:27:56 +01:00
102a860ba3 Added scan delete tests
ref #67
2021-01-08 16:47:52 +01:00
3a886714a0 Merge branch 'feature/67-scan_apis' of git.odit.services:lfk/backend into feature/67-scan_apis
# Conflicts:
#	src/tests/scanstations/scanstations_delete.spec.ts
2021-01-07 20:35:05 +01:00
09ab638239 Added scan station delete tests
ref #67
2021-01-07 20:34:48 +01:00
a4f88c78f4 Added scan station delete tests
ref #67
2021-01-07 20:34:36 +01:00
ccf2a3b617 Added scan station update tests
ref #67
2021-01-07 20:31:29 +01:00
c8f941a779 Fixed wrong error getting thrown
ref #67
2021-01-07 20:22:58 +01:00
5510cbb8e9 Added scan station add tests
ref #67
2021-01-07 20:16:14 +01:00
a434173b54 Added scan station get tests
ref #67
2021-01-07 20:04:15 +01:00
7387f700fb Added alias for posting track scans
ref #67
2021-01-07 19:46:20 +01:00
4f01baaa23 Added the enabled flag for scanstations
ref #67
2021-01-07 19:37:15 +01:00
09b37f0ff2 Fixed typo
ref #67
2021-01-07 19:36:57 +01:00
324d5709e3 Added tmp files to gitignore
ref #67
2021-01-07 19:19:21 +01:00
3f23e4f1f1 Added scan get tests
ref #67
2021-01-07 19:18:26 +01:00
9776a35f9f Track deletion now recognizes associated stations
ref #67
2021-01-07 18:53:09 +01:00
9b9ee70288 Implemented cascading station deletion
ref #67
2021-01-07 18:48:58 +01:00
2628f69651 Implemented scan station creation
ref #67
2021-01-07 18:39:38 +01:00
b9c0a32862 Implemented single scan station get +e errors
ref #67
2021-01-07 18:35:19 +01:00
82644a2ff4 Implmented getting all scan stations
ref #67
2021-01-07 18:05:54 +01:00
3d2c93b5ac Added (scan) stations as a new permission target
ref #67
2021-01-07 17:35:36 +01:00
c447114297 Added a ScanStation response class
ref #67
2021-01-07 17:31:44 +01:00
857de9ffcc Added Creation class for ScanSatations
ref #67
2021-01-07 17:29:22 +01:00
eea656bd7b Added a barebones scanstation controller
ref #67
2021-01-07 17:16:36 +01:00
eec5284306 Implemented "normal" scan updateing
ref #67
2021-01-07 17:12:12 +01:00
88a6a768c4 Implemented scan deletion
ref #67
2021-01-07 17:03:40 +01:00
edac1a224c Fixed runner scan validation bug
ref #67
2021-01-07 16:59:57 +01:00
e67d1c5697 Fixed scan runner in response
ref #67
2021-01-07 16:38:41 +01:00
30502ec949 Fixed Creation of normal scans
ref #67
2021-01-07 16:32:16 +01:00
a2c3913601 Merge branch 'feature/67-scan_apis' of git.odit.services:lfk/backend into feature/67-scan_apis 2021-01-07 16:13:44 +01:00
f1c7713da2 Adusted the way scan distances are implemented
ref #67
2021-01-07 16:13:41 +01:00
d6a41d5a82 Ajusted the way scan distances are implemented 2021-01-07 16:13:31 +01:00
72b5ca4153 Added basics for scan creation (to be tested after scanstations got added)
ref #67
2021-01-06 19:44:20 +01:00
aeec2e1c32 Added single scan get w/ errors
ref #67
2021-01-03 19:36:38 +01:00
f9889bea3d Implemented scans get including the response classes
ref #67
2021-01-03 19:26:06 +01:00
2cad2ac2e9 Implemented the second round of the toResponse normalisationf for all classes
ref #67
2021-01-03 19:18:31 +01:00
d948fe2631 Merge pull request 'Fixed relative paths not being updated + version bump for bugfix release' (#75) from dev into main
Some checks failed
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #75
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-03 18:13:10 +00:00
2b5525323b Fixed relative paths not being updated + version bump for bugfix release
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2021-01-03 19:10:46 +01:00
58156e0d61 Implemented the first route of the toResponse normalisationf for all classes
ref #67
2021-01-03 19:09:06 +01:00
a4b0dfe43e Defined responses for scans and trackscans
ref #67
2021-01-03 19:02:06 +01:00
ee2433a5ae Added barebones scans controller
ref #67
2021-01-03 18:49:33 +01:00
2151b8502d Added Scan permission target
ref #67
2021-01-03 18:48:05 +01:00
b57fde9b0a Merge pull request 'Bugfix for the openapi exporter' (#74) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #74
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-03 17:29:35 +00:00
86706f9422 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-03 17:29:24 +00:00
0687f268fc Fixed switch up between node/js and ts-node/ts
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-03 18:27:58 +01:00
bc426831db Merge pull request 'Alpha Release 0.0.7' (#73) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
Reviewed-on: #73
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2021-01-03 17:22:25 +00:00
276e553e13 Version bump
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
ref #73
2021-01-03 18:18:51 +01:00
e7ab302c61 Merge pull request 'Minimum lap times for tracks feature/71-track_times' (#72) from feature/71-track_times into dev
Some checks failed
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is failing
Reviewed-on: #72
closes #71
2021-01-03 17:17:59 +00:00
a5d70ce4b5 Removed useless console.log
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-03 18:13:53 +01:00
d67be313e6 Added track update tests
All checks were successful
continuous-integration/drone/pr Build is passing
ref #71
2021-01-03 18:08:04 +01:00
15d2d029dc Added track delete tests
ref #71
2021-01-03 18:07:33 +01:00
b6ea5e6549 Fixed copy-paste mistake
ref #71
2021-01-03 18:01:21 +01:00
f378b0651a Added helpful comment about the tracktime's unit
ref #71
2021-01-03 17:52:36 +01:00
1a0573e0d0 Added track add tests
ref #71
2021-01-03 17:52:16 +01:00
9f103d8df1 Added track get tests
ref #71
2021-01-03 17:49:55 +01:00
daa899a1ef Removed the old basic test class
ref #71
2021-01-03 17:49:44 +01:00
59cb72a11d Implemented track upodates using the "new" method
ref #71
2021-01-03 17:30:17 +01:00
28c1b6d31d Improved error handling for negative lap times
ref #71
2021-01-03 17:21:53 +01:00
dcb791c9a2 Added the laptime to the track response
ref #71
2021-01-03 17:06:57 +01:00
907259bf73 Added the laptime to createtrack
ref #71
2021-01-03 17:05:43 +01:00
02f7ddbb37 Marked property as optional
ref #71
2021-01-03 17:04:09 +01:00
63b1ca9b56 Added the minimum lap time to the track entity
ref #71
2021-01-03 16:51:36 +01:00
39857cf6e6 Merge pull request 'New Feature: Donor endpoints feature/65-donor_controllers' (#69) from feature/65-donor_controllers into dev
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #69
closes #65

Donors go 💲💲💲
2021-01-02 21:06:08 +00:00
3090ae69f3 Merge branch 'dev' into feature/65-donor_controllers
All checks were successful
continuous-integration/drone/pr Build is passing
2021-01-02 20:55:31 +00:00
92186a86cc Merge pull request 'bugfix/68-address_circular_dependencies' (#70) from bugfix/68-address_circular_dependencies into feature/65-donor_controllers
All checks were successful
continuous-integration/drone/pr Build is passing
Reviewed-on: #70
closes #68
2021-01-02 20:55:02 +00:00
97e8470b0d Change requested by @philipp
All checks were successful
continuous-integration/drone/pr Build is passing
ref #70
2021-01-02 21:53:21 +01:00
6b0e3503a7 Dependency: Bumped license-exporter version
All checks were successful
continuous-integration/drone/push Build is passing
ref odit/license-exporter#1 odit/license-exporter#3
2021-01-02 20:49:20 +01:00
1e2de7656e Reenabled addresses in org responses
All checks were successful
continuous-integration/drone/pr Build is passing
ref #68
2021-01-02 20:03:02 +01:00
56c6a7efb0 Revert "Removed addresses from tests until the circular dependencies are solved"
This reverts commit 599296c4e3.
2021-01-02 19:57:55 +01:00
9c4e54fc6e Added comments to the bugfix 2021-01-02 19:57:33 +01:00
2c47436259 Implemented a possible bugfix
ref #68
2021-01-02 19:56:04 +01:00
9b5d16ae92 Added todo relateing to the bugfix issue
All checks were successful
continuous-integration/drone/pr Build is passing
ref #65 #68
2021-01-02 19:39:02 +01:00
deb13674b2 Added donor put (update) tests
ref #65
2021-01-02 19:25:58 +01:00
17c82ff409 Added donor delete tests
ref #65
2021-01-02 19:13:59 +01:00
f9e314bf9f Added donor add test for address needed error
ref #65
2021-01-02 19:12:02 +01:00
e4c1930dd1 Added donor post (add) tests
ref #65
2021-01-02 19:10:23 +01:00
b337ab424d Added donor get tests
ref #65
2021-01-02 19:02:31 +01:00
82a0e194cb Updated track tests for paralellism
ref #65
2021-01-02 19:02:16 +01:00
599296c4e3 Removed addresses from tests until the circular dependencies are solved
ref #65
2021-01-02 19:01:55 +01:00
2594a607dc Added address check for donors that want a receipt on update
ref #65
2021-01-02 18:30:03 +01:00
335d4e24da Added address check for donors that want a receipt
ref #65
2021-01-02 18:28:22 +01:00
becc277123 Merge branch 'feature/65-donor_controllers' of git.odit.services:lfk/backend into feature/65-donor_controllers 2021-01-02 18:19:51 +01:00
52cdd41ec8 Fixed not null constraint
ref #65
2021-01-02 18:19:45 +01:00
53548ba7a6 Fixed not null constraint
ref #56
2021-01-02 18:19:40 +01:00
1dc438beb2 Mitigated circular dependency (to be fixed)
ref #65
2021-01-02 18:12:18 +01:00
c9ba69792f Extended todo w/ issue link
ref #65
2021-01-02 17:07:17 +01:00
ab67e5f4aa Added basic runner updateing
ref #65
2021-01-02 16:55:27 +01:00
557608e318 Added everything for basic donor creation
ref #65
2021-01-02 16:51:33 +01:00
a83fedc9b8 Added first donor-specific errors
ref #65
2021-01-02 16:47:06 +01:00
61a17b198f Implemented basic donor deletion
ref #65
2021-01-02 16:45:01 +01:00
3df1db4ad8 Added the base logic for donor getters
ref #65
2021-01-02 16:42:55 +01:00
e46cfa0d77 Added donor response class
ref #65
2021-01-02 16:40:38 +01:00
4126d31a5e Added copy of runnerController with some stuff reanames for donors
ref #65
2021-01-02 16:38:07 +01:00
9d9549cdd4 Added new donor permission target
ref #65
2021-01-02 16:37:17 +01:00
eb40de6eb4 Removed legacy license txt file
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-31 18:05:21 +01:00
6efd09db73 new license file version [CI SKIP] 2020-12-31 17:02:56 +00:00
3f09e3d387 Merge pull request 'Automatic and manual license collection 📖' (#62) from feature/59-license_collection into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #62
closes #59
2020-12-31 17:02:08 +00:00
05868e0e00 Bumped license lib version
All checks were successful
continuous-integration/drone/pr Build is passing
ref #59
2020-12-31 18:00:46 +01:00
580a73f9a5 Switched to automatic license attribution generation via oss-attribution-generator
All checks were successful
continuous-integration/drone/pr Build is passing
#59
2020-12-31 15:14:51 +01:00
ab7110d49f Merge branch 'dev' into feature/59-license_collection
All checks were successful
continuous-integration/drone/pr Build is passing
# Conflicts:
#	.drone.yml
2020-12-30 21:26:07 +01:00
875781335c Removed the testing pipeline and updated the dev license pipeline
ref #59
2020-12-30 21:24:51 +01:00
625340cf8a Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-30 21:23:37 +01:00
8d9dbc3957 Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection 2020-12-30 21:23:33 +01:00
07d813082b Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection 2020-12-30 21:23:04 +01:00
a684f60252 Added secondary dependency for piupeline
ref #59
2020-12-30 21:22:59 +01:00
931cae3c98 new license file version [CI SKIP] 2020-12-30 20:22:35 +00:00
dfd82a6293 Merge branch 'feature/59-license_collection' of git.odit.services:lfk/backend into feature/59-license_collection
All checks were successful
continuous-integration/drone/push Build is passing
# Conflicts:
#	.drone.yml
2020-12-30 21:21:50 +01:00
82d4b11de3 Adjusted ci dependencies
ref #59
2020-12-30 21:21:40 +01:00
75473937cf Adjusted ci dependencies
ref #59
2020-12-30 21:21:08 +01:00
a68bbab8ab Canged drone branch
All checks were successful
continuous-integration/drone/push Build is passing
ref #59
2020-12-30 21:18:40 +01:00
5cfd2c9a52 Revert "Added license exporter (to json)"
All checks were successful
continuous-integration/drone/push Build is passing
This reverts commit 84a0bd2cd9.
2020-12-30 21:17:27 +01:00
6c7b31d76c Revert "Moved package script related files to their own folder"
This reverts commit 395b0101a8.
2020-12-30 21:17:23 +01:00
2924ac2900 Revert "Added automatic license export on dev push/merge"
This reverts commit 18e3ef9a79.
2020-12-30 21:17:18 +01:00
a501625dd6 Revert "Added --full option for the license exporter to export the license path and text as well"
This reverts commit 62c7f26540.
2020-12-30 21:17:13 +01:00
cc64ce4498 Revert "Added test pipeline for automatic license export"
This reverts commit c9378e6cae.
2020-12-30 21:17:09 +01:00
c9378e6cae Added test pipeline for automatic license export
All checks were successful
continuous-integration/drone/push Build is passing
ref #59
2020-12-30 21:13:32 +01:00
62c7f26540 Added --full option for the license exporter to export the license path and text as well
ref #59
2020-12-30 21:05:16 +01:00
18e3ef9a79 Added automatic license export on dev push/merge
ref #59
2020-12-30 20:40:28 +01:00
395b0101a8 Moved package script related files to their own folder
ref #59
2020-12-30 20:22:18 +01:00
84a0bd2cd9 Added license exporter (to json)
ref #59
2020-12-30 20:18:28 +01:00
9cd181c5b8 Merge pull request 'Merge for alpha 0.0.6' (#61) from dev into main
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #61
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2020-12-30 17:58:24 +00:00
41828a6e41 Version bump
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-12-30 18:55:16 +01:00
356e398caf Merge pull request 'feature/56-stats_endpoint' (#60) from feature/56-stats_endpoint into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #60
closes #56
2020-12-30 16:49:18 +00:00
6cb978df98 Updated security for the stats endpoints
All checks were successful
continuous-integration/drone/pr Build is passing
ref #56 requested by @philipp
2020-12-30 17:40:18 +01:00
4cb0efa6bd Added response schemas
All checks were successful
continuous-integration/drone/pr Build is passing
ref #56
2020-12-30 17:35:21 +01:00
e0fa58da57 Added some comments
All checks were successful
continuous-integration/drone/pr Build is passing
ref #56
2020-12-30 17:27:24 +01:00
5d31d8d1a2 Added stats and stats responses for orgs
ref #56
2020-12-30 16:59:07 +01:00
53a01ad977 Added stats response
ref #56
2020-12-30 16:31:18 +01:00
d7791756dc Added mission relation resolving
ref #56
2020-12-30 16:13:57 +01:00
dd48ee2f7e Added ResponseSchemas and fixed donation resolution bug
ref #56
2020-12-30 15:07:13 +01:00
ec64ec3d63 Added a response class for team stats
ref #56
2020-12-30 14:41:07 +01:00
35dbfeb5e7 Added donation amount to the stats runner response
ref #56
2020-12-30 14:34:10 +01:00
a9ecfccfd2 Added response schemas
ref #56
2020-12-30 14:31:07 +01:00
d850650aeb Added response class for the runner stats
ref #56
2020-12-30 14:30:31 +01:00
43e256f38c Impelemented stats api auth via token or the usual auth (jwt with get for runners, teams and orgs).
ref #56
2020-12-30 14:19:54 +01:00
b5f9cf201d Moved the authchecker to the middleware folder (b/c it pretty much is a glolified middleware)
ref #56
2020-12-30 14:01:37 +01:00
6e121a3ce2 Implemented more stats endpoints
ref #56
2020-12-29 22:17:29 +01:00
555e37eaf7 Added authed stats routes
ref #56
2020-12-29 21:48:21 +01:00
9675e79441 Added openapi scheme for the stats api tokens.
ref #56
2020-12-29 21:38:48 +01:00
345851bf1d Added example endpoint for stats auth 2020-12-29 21:34:49 +01:00
7c5a3893ef Added basic status api key checking middleware
ref #56
2020-12-29 21:32:45 +01:00
b53b5cf91f Update: keys cant be updated (for security reasons)
ref #56
2020-12-29 21:00:43 +01:00
04813173e4 Updated the method of api key creation.
ref #56
2020-12-29 20:49:45 +01:00
c4270b0839 Adapted the new async behaviour
ref #56
2020-12-29 20:21:45 +01:00
bb24ed53a4 Switched to hased tokens based on uuid (to be canged)
ref #56
2020-12-29 20:20:59 +01:00
1b74b21420 Renamed class
ref #56
2020-12-29 20:07:43 +01:00
b7cbe2a0b4 Adjusted the validation type
ref #56
2020-12-29 20:05:35 +01:00
500b94b44a Added a controller for stats clients (todo: put)
ref #56
2020-12-29 20:01:40 +01:00
641466a731 Added basic errors for stats clients
ref #56
2020-12-29 20:00:31 +01:00
e3ea83bb47 Removed async flag, b/c this doesn't need to perform anything async
ref #56
2020-12-29 19:57:19 +01:00
b6043744a9 Added STATSCLIENT as a new permission target
ref #56
2020-12-29 19:48:35 +01:00
2b38044271 Created a response for the statsClient
ref #56
2020-12-29 19:45:30 +01:00
4c3d2643c1 Added enabled flag for the stats clients
ref #56
2020-12-29 19:37:55 +01:00
e2cc0c0b80 Added Create action for the statsclients
ref #56
2020-12-29 19:34:14 +01:00
ce55dce011 Removed abstract flag from class
ref #56
2020-12-29 19:32:20 +01:00
a738c19316 Added the new statsClient class for stats api auth
ref #56
2020-12-29 19:29:16 +01:00
63b8176bdf Merge branch 'dev' into feature/56-stats_endpoint 2020-12-29 19:18:56 +01:00
bc76afafce Merge pull request 'Updates for the tag build pipeline' (#58) from dev into main
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
Reviewed-on: #58
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2020-12-29 18:08:09 +00:00
1f49ad43a1 Merge branch 'main' into dev
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-12-29 18:07:47 +00:00
6a762f570d Added team and org stats
ref #56
2020-12-29 16:08:50 +01:00
1b7424f750 Added stats endpoint with some basic stats (more to come) - to be tested
ref #56
2020-12-29 15:25:40 +01:00
bdd4f705be Adjusted return type, since async is no longer needed here (thanks to db relations)
ref #56
2020-12-29 15:23:29 +01:00
ded14b1b3b Changed method of triggering lib builds
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-12-29 14:57:00 +01:00
fbd3f615ad Changed docker image tag
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-29 14:56:37 +01:00
a22a7a19c2 Merge pull request 'Final fix for the tag pipeline triggers' (#57) from dev into main
Some checks failed
continuous-integration/drone/tag Build was killed
Reviewed-on: #57
Reviewed-by: odit_bot <bot@odit.services>
2020-12-29 13:18:39 +00:00
2d263814db Merge branch 'main' into dev 2020-12-29 13:17:33 +00:00
a79bed259b Moved to the official tag recognition
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-29 14:13:48 +01:00
f2970f4cd8 Added branch to when
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 19:09:01 +01:00
b3f741234e Back to when syntax for triggering tag builds
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:51:07 +01:00
6a8247f88a Now using the exact trigger snytax the gitea project uses
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:46:38 +01:00
b737fe6a08 Set trigger to ref tags only
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:39:44 +01:00
607630c4f9 Tried switching to global when
Some checks failed
continuous-integration/drone/push Build was killed
2020-12-23 18:38:01 +01:00
a7976c0ee2 Switched from trigger to when
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:36:37 +01:00
b51da15007 Added pushing to tags as trigger
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:33:56 +01:00
5ed5f181d1 Added tag as ref to tag build
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:32:32 +01:00
e33076c04d Removed push from tag build triggers
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:30:08 +01:00
ae35f50da2 Added push as drone tag build event trigger
Some checks failed
continuous-integration/drone/push Build was killed
2020-12-23 18:28:10 +01:00
cc5d90cb4f Merge pull request 'Bugfix for the release pipeline (no other changes)' (#55) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #55
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>
2020-12-23 17:18:29 +00:00
c33236c516 Merge branch 'main' into dev
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-12-23 18:11:47 +01:00
eee2bbcac7 Merge branch 'dev' of git.odit.services:lfk/backend into dev
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-23 18:11:23 +01:00
519d11beef Removed the branch requirements from dev
ref #47
2020-12-23 18:11:20 +01:00
cbed5fc0b2 Merge pull request 'Merge alpha 0.0.5 to master' (#54) from dev into main
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #54
Reviewed-by: Philipp Dormann <philipp@philippdormann.de>

ref #47
2020-12-23 17:05:32 +00:00
59fdfe9f40 Merge branch 'main' into dev
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2020-12-23 17:02:12 +00:00
c93e93be31 Set package version (+openapi version)
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
ref #47
2020-12-23 18:00:53 +01:00
d3760f7b80 Merge pull request 'feature/52-alternative_openapi_viewers' (#53) from feature/52-alternative_openapi_viewers into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #53
closes #52
2020-12-23 16:57:17 +00:00
11c7d041ef 🎨 fixed landing html + styling
All checks were successful
continuous-integration/drone/pr Build is passing
ref #52
2020-12-23 17:55:44 +01:00
9ab6eb5314 Added tests for the api docs
All checks were successful
continuous-integration/drone/pr Build is passing
ref #52
2020-12-23 17:01:18 +01:00
ce0500ef8c Removed the firsttests jest tests (they were redundant)
ref #52
2020-12-23 17:01:03 +01:00
0b4d30b3f3 Updated the openapi json path for the ci testing script
Some checks failed
continuous-integration/drone/pr Build is failing
ref #52
2020-12-23 16:54:45 +01:00
bb70bf58fb Added the static files to the build step
Some checks failed
continuous-integration/drone/pr Build is failing
ref #52
2020-12-23 15:43:03 +01:00
9fc282d858 Removed everything concerning the swaggerUI express middleware
ref #52
2020-12-23 15:21:55 +01:00
39ad43bbb2 switched over to using the static deployment of swaggerUI
ref #52
2020-12-23 15:20:06 +01:00
bd46a48f76 Merge branch 'feature/52-alternative_openapi_viewers' of git.odit.services:lfk/backend into feature/52-alternative_openapi_viewers 2020-12-23 15:11:17 +01:00
ebedea97ed Added very basic api doc chooser
ref #52
2020-12-23 15:11:14 +01:00
5c3c3eb167 Added very basic api doc chooser
ref #52
2020-12-23 15:11:04 +01:00
d8e38f404d Renamed the package to fit the scheme for the project
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 20:43:49 +01:00
aa1042ca51 Merge pull request 'feature/49-openapi_cookie_schema' (#51) from feature/49-openapi_cookie_schema into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #51
closes #49
2020-12-22 19:33:03 +00:00
9994f8ddc4 Merge branch 'dev' into feature/49-openapi_cookie_schema
All checks were successful
continuous-integration/drone/pr Build is passing
2020-12-22 19:32:35 +00:00
3ac536ef23 Merge pull request 'feature/45-auth_tests' (#50) from feature/45-auth_tests into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #50
closes #45
2020-12-22 19:32:20 +00:00
5d75f70296 fixed typo
All checks were successful
continuous-integration/drone/pr Build is passing
ref #49
2020-12-22 20:29:18 +01:00
c34bde7d4f Fixed typo
All checks were successful
continuous-integration/drone/pr Build is passing
ref #49
2020-12-22 20:19:28 +01:00
1f061c7ea6 Updated the openapi descriptions for all group routes
ref #49
2020-12-22 20:18:30 +01:00
578f9301db Updated the openapi descriptions for all user routes
ref #49
2020-12-22 20:13:16 +01:00
9b47f3ab05 Updated the openapi descriptions for all track routes
ref #49
2020-12-22 20:07:41 +01:00
84b97bee8d Updated the openapi descriptions for all status routes
ref #49
2020-12-22 20:05:29 +01:00
767841d405 Merge branch 'feature/49-openapi_cookie_schema' of git.odit.services:lfk/backend into feature/49-openapi_cookie_schema
# Conflicts:
#	src/controllers/RunnerTeamController.ts
2020-12-22 20:04:19 +01:00
16e5b6921d Updated the openapi descriptions for all team routes
ref #49
2020-12-22 20:04:08 +01:00
58a12c7fa1 Updated the openapi descriptions for all team routes
ref #49
2020-12-22 20:03:49 +01:00
f256dec121 Updated the openapi descriptions for all organisation routes
ref #49
2020-12-22 20:01:25 +01:00
9bb4865b2d Merge branch 'feature/49-openapi_cookie_schema' of git.odit.services:lfk/backend into feature/49-openapi_cookie_schema
# Conflicts:
#	src/controllers/RunnerController.ts
2020-12-22 19:58:25 +01:00
66631f5e0a Updated the openapi descriptions for all runner routes
ref #49
2020-12-22 19:57:46 +01:00
8de35f3431 Updated the openapi descriptions for all runner routes
ref #49
2020-12-22 19:55:37 +01:00
05319e6f6e Updated the openapi descriptions for all permission routes
ref #49
2020-12-22 19:51:37 +01:00
b7827fef54 Updated the openapi descriptions for all import routes
ref #49
2020-12-22 19:45:09 +01:00
a4ddeee8e4 Fixed uniqueness error
All checks were successful
continuous-integration/drone/pr Build is passing
ref #45
2020-12-22 19:38:12 +01:00
50f2462eb9 Updated the openapi descriptions for all auth routes
ref #49
2020-12-22 19:23:35 +01:00
dae51cfd47 Added openapi cookie security schema
ref #49
2020-12-22 19:13:20 +01:00
e1341fc126 Merge branch 'dev' into feature/45-auth_tests
Some checks failed
continuous-integration/drone/pr Build is failing
2020-12-22 18:50:13 +01:00
a9dbf1d0d2 Added login test after logout
Some checks failed
continuous-integration/drone/pr Build is failing
ref #45
2020-12-22 18:49:10 +01:00
c6ecde29b5 Added auth reset tests
ref #45
2020-12-22 18:48:54 +01:00
13949af938 Added auth refresh tests
ref #45
2020-12-22 18:29:23 +01:00
3c003a60b2 Added logut tests
ref #45
2020-12-22 18:26:20 +01:00
69796a888f Added wron password auth test
ref #45
2020-12-22 17:04:22 +01:00
a85e914759 Added validator as a explicit dependency, b/c pnpm doesn't fallback to peer dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 16:57:01 +01:00
af2744885f added the first login tests
ref #45
2020-12-22 16:56:02 +01:00
8d73a9dd59 Merge pull request 'feature/40-pw_reset' (#48) from feature/40-pw_reset into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #48
closes #40
2020-12-22 15:29:43 +00:00
853876a09c Merge branch 'dev' into feature/40-pw_reset
All checks were successful
continuous-integration/drone/pr Build is passing
2020-12-22 16:05:27 +01:00
cdc90b0770 Merge pull request 'feature/43-postal_from_env' (#46) from feature/43-postal_from_env into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #46
closes #43
2020-12-22 14:55:37 +00:00
d0cfc16f8b Merge branch 'dev' into feature/43-postal_from_env
All checks were successful
continuous-integration/drone/pr Build is passing
2020-12-22 15:55:12 +01:00
ce5f4b467d Updated ci to trigger the builds for the new libs
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-22 15:44:00 +01:00
84a7f30a60 Merge branch 'dev' into feature/43-postal_from_env
All checks were successful
continuous-integration/drone/pr Build is passing
2020-12-22 12:41:15 +01:00
f3008979f3 Added the POSTALCODE_COUNTRYCODE to the sample and ci env files
ref #43
2020-12-22 12:40:11 +01:00
b8c93bf476 Implemented the getter for loading the postalcodelocale from env
ref #43
2020-12-22 12:38:53 +01:00
146787fd66 Added comments
All checks were successful
continuous-integration/drone/pr Build is passing
ref #40
2020-12-22 11:48:06 +01:00
9458b774ea Removed the user disableing
ref #40
2020-12-22 11:35:33 +01:00
bf4250babd All things auth now check if the user is disabled
ref #40
2020-12-22 11:29:52 +01:00
a16c4c564a Users now can be disabled from the start
ref #40
2020-12-22 11:27:21 +01:00
8d860cb2e1 Fixed weired query behaviour
ref #40
2020-12-22 11:26:45 +01:00
2f7b0d5606 Removed bs enabled check
ref #40
2020-12-22 11:20:11 +01:00
4b9bfe3b79 Now disableing users while they're in the process of resetting their password
ref #40
2020-12-22 11:18:31 +01:00
17ee682029 Implemented a password reset timeout
ref #40
2020-12-22 11:12:24 +01:00
48685451be Set reset token expiry to 15 mins
rer #40
2020-12-22 11:07:01 +01:00
5aad581c2d Implemented toe password reset route
ref #40
2020-12-22 10:57:25 +01:00
caeb17311b Implemented basic password reset
ref #40
2020-12-22 10:57:08 +01:00
5aa83fe2f0 Renamed the return variable to fit the class
ref #40
2020-12-22 10:44:43 +01:00
aef8485f59 Renamed the password reset token creation class to better fit the scheme
ref #40
2020-12-22 10:39:42 +01:00
61aff5e629 Added a password reset token request route
ref #40
2020-12-22 10:39:17 +01:00
aa146cd6c1 Added a basic pw reset action
ref #40
2020-12-22 10:38:48 +01:00
6042089074 Added pw reset jwt generation
ref #40
2020-12-22 10:24:25 +01:00
b6cf3b24d4 Merge pull request 'Disabled the x-served-by and x-powered-by Headers' (#44) from feature/41-owasp_headers into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #44
2020-12-21 17:27:12 +00:00
19422edbae Disabled the x-served-by and x-powered-by Headers
All checks were successful
continuous-integration/drone/pr Build is passing
ref #41
2020-12-21 17:48:04 +01:00
1bf6d3d564 Merge pull request 'Updated the put methods and cleaned up a shitload of comments' (#42) from feature/39-update_puts into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #42
closes #39
2020-12-21 16:39:19 +00:00
7d5f3b092f Reverted simplification that created loops
All checks were successful
continuous-integration/drone/pr Build is passing
ref #39
2020-12-21 17:22:07 +01:00
0ef6d9cc48 Small bugfix
Some checks failed
continuous-integration/drone/pr Build is failing
ref #39
2020-12-21 16:58:51 +01:00
48bef8db60 Second part of the action comment refactoring
Some checks failed
continuous-integration/drone/pr Build is failing
ref #39
2020-12-21 16:21:12 +01:00
1d0d79f3da First part of the action comment refactoring
ref #39
Why did i volunteer for this? It's just a glorified sleeping aid 😴
2020-12-21 16:08:10 +01:00
d20d738218 Code + comment cleanup for the entities
ref #39
2020-12-21 15:29:32 +01:00
a03f1a438d Code + comment cleanup for the enums
ref #39
2020-12-20 19:39:13 +01:00
75332983c2 Code + comment cleanup for the response models
ref #39
2020-12-20 19:38:22 +01:00
a85d52437b Code + comment cleanup for the seeds
ref #39
2020-12-20 19:11:28 +01:00
a88c0389c1 Code + Comment cleanup for the middlewares
ref #39
2020-12-20 19:08:02 +01:00
43a4f1118d Updated loader comments and descriptions
ref #39
2020-12-20 19:01:03 +01:00
de91d491e5 Added a missing poiunt/exclamation mark
ref #39
2020-12-20 18:55:52 +01:00
2199cb0aef Fixed messages and comments for UserGroupErrors
ref #39
2020-12-20 18:50:45 +01:00
ee76f1c0e8 Fixed messages and comments for UserErrors
ref #39
2020-12-20 18:50:06 +01:00
75b6489f8d Fixed messages and comments for TrackErrors + spelling for some other errors
ref #39
2020-12-20 18:48:59 +01:00
389f6347c3 Fixed messages and comments for RunnerTeamErrors
ref #39
2020-12-20 18:47:42 +01:00
37afc10e44 Fixed messages and comments for RunnerOrganisationErrors
ref #39
2020-12-20 18:46:43 +01:00
82ced34750 Fixed messages and comments for RunnerGroupErrors
ref #39
2020-12-20 18:45:33 +01:00
5de81ad093 Fixed messages and comments for RunnerErrors
ref #39
2020-12-20 18:45:10 +01:00
c1d784e29c Fixed messages and comments for PermissionErrors
ref #39
2020-12-20 18:42:57 +01:00
4ca85a1f22 Fixed messages and comments for AuthErrors
ref #39
2020-12-20 18:41:25 +01:00
7a4238f1f7 Fixed some stuff not getting checked against null
ref #39 gosh i sometimes hate js types
2020-12-20 18:18:32 +01:00
fbe2b358bd Moved tracks to the new put mechanism
ref #39
2020-12-20 18:07:45 +01:00
532b5a56a5 Switched runner orgs to the cleaner syntax via a update entity
ref #39
2020-12-20 18:07:33 +01:00
18ede29ea5 Moved usergroups to the new put mechanism
ref #39
2020-12-20 17:37:20 +01:00
ec4d75128b Fixed some weired toString beviour
ref #39 #6
2020-12-20 17:36:22 +01:00
b2bd6173a5 Moved permissions to the new put mechanism
ref #39
2020-12-20 17:29:04 +01:00
cc68948a20 Moved runners to the new put mechanism
ref #39
2020-12-20 17:27:21 +01:00
24de82f6df Moved runner teams to the new put mechanism
ref #39
2020-12-20 17:25:41 +01:00
cf583a22fa Added missing username property to the responseuser
ref #39
2020-12-20 17:19:33 +01:00
b55d210aff Fixed wrong error type
ref #39
2020-12-20 17:18:16 +01:00
adec2bcc5b removed useless deletes
ref #39
2020-12-20 17:17:42 +01:00
3850bd9681 Renamed function to better reflect it's function
ref #39
2020-12-20 17:17:30 +01:00
14b6651f96 Merge pull request 'Fixed a bug concerning user updates' (#38) from bugfix/37-user_update into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #38
closes #37
2020-12-20 15:58:19 +00:00
4a21c1fb5c Updated some nameing to fit with the rest of the models
All checks were successful
continuous-integration/drone/pr Build is passing
ref #37
2020-12-20 16:51:59 +01:00
ca142376b3 Fixed some weired user update behaviour
ref #37
2020-12-20 16:49:05 +01:00
314606addd Merge pull request 'feature/34-status_health' (#36) from feature/34-status_health into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #36
closes #34
2020-12-18 22:02:34 +00:00
a0a08f7724 Formatting implemented for @philipp
All checks were successful
continuous-integration/drone/pr Build is passing
ref #34
2020-12-18 23:00:27 +01:00
cea5993049 added a simple health route
ref #34
2020-12-18 22:58:24 +01:00
3e940c2db5 Merge pull request 'Auth for everything (and everything auth) #6' (#35) from feature/6-api_auth into dev
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #35

closes #6
2020-12-18 21:53:16 +00:00
631310f158 Fixed import for linux
All checks were successful
continuous-integration/drone/pr Build is passing
ref #6
2020-12-18 22:49:01 +01:00
c3e3c6bed1 Manual overwrite
Some checks failed
continuous-integration/drone/pr Build is failing
ref #6
2020-12-18 22:46:59 +01:00
8f48d2593b Merge branch 'feature/6-api_auth' of git.odit.services:lfk/backend into feature/6-api_auth
Some checks failed
continuous-integration/drone/pr Build is failing
2020-12-18 22:44:29 +01:00
23758e7a91 Bugfix for bs file names
ref #6
2020-12-18 22:44:27 +01:00
c7fd0593fb Bugfix for bs file names
Some checks failed
continuous-integration/drone/pr Build is failing
ref #6
2020-12-18 22:42:48 +01:00
b19f18ada1 Added auth to all tests
Some checks failed
continuous-integration/drone/pr Build is failing
ref #6
2020-12-18 22:11:41 +01:00
d742ccd581 Jwt's now feature group permissions and permission deduplication
ref #6
2020-12-18 21:44:30 +01:00
d670b814a4 Fixed the user->Group relation
ref #6
2020-12-18 21:42:43 +01:00
1a9c860188 Formatting #6 2020-12-18 20:53:35 +01:00
f25ae9ba4f Added a admin group with all permissions to seeding 2020-12-18 20:33:27 +01:00
744faba7ee Added auth to all endpoints 2020-12-18 20:33:13 +01:00
cdfd0e0d64 Added the openapi security header to all routes that need some kind of auth
ref #6
2020-12-18 20:07:05 +01:00
e25fc795fe Added additional targets and actions for permissions
ref #6
2020-12-18 20:06:27 +01:00
2240a45a91 Added class validation for the enum
ref #6
2020-12-18 19:49:39 +01:00
595a9213c1 Added comments and formatting to the auth checker
ref #6
2020-12-18 19:42:08 +01:00
428e2c38ce Added coments to the jwt creator 2020-12-18 19:33:10 +01:00
1d54fb085b Shoothed out variable nameing scheme
ref #6
2020-12-18 19:19:47 +01:00
6403e386ab Now with smooth access token refreshing
ref #6
2020-12-18 19:07:31 +01:00
65a8449ea3 Now with 1000% cleaner jwt generation
ref #6
2020-12-18 17:57:48 +01:00
b21dd6f0c0 Added tracks/get as test-route for auth
ref #6
2020-12-18 17:19:02 +01:00
445e96dcdf Added toString for permissions
ref #6
2020-12-18 17:17:02 +01:00
6237e62a03 Reimplmented the old permission checking system
ref #6
2020-12-18 17:15:44 +01:00
b9e91502cd Cleaned up the auth checker a little bit 2020-12-18 17:11:44 +01:00
9dc336f0bb Added permission deletion on group deletion
ref #6
2020-12-18 16:21:59 +01:00
6a7e8ccc37 Now with duplication avoidance
ref #6
2020-12-18 16:10:33 +01:00
882065470a Implemented permission updateing
ref #6
2020-12-18 16:05:25 +01:00
ff3a5b4545 User deletion now also delete's the users permissons
ref #6
2020-12-18 15:49:42 +01:00
d4293c164d Implemented permission deletion
ref #6
2020-12-18 15:37:45 +01:00
145a08b1b4 Now with cleaner participants in the responses
ref #6
2020-12-18 15:26:54 +01:00
dc485c02ea Added Permission creation
ref #11
2020-12-18 15:19:44 +01:00
ebb0c5faca Added specific permission getting
ref #6
2020-12-18 15:16:15 +01:00
d89fcb84a2 Implemented permission getting
ref #6
2020-12-18 15:12:06 +01:00
388fc6ba6a Fixed typo 2020-12-18 14:52:31 +01:00
bb4ea485fd Merge branch 'dev' into feature/6-api_auth 2020-12-18 14:40:40 +01:00
5dc9edfe40 Pulled out some linguini-esc code
ref #6
2020-12-18 14:40:19 +01:00
eb9473e230 Cleaned up relation types
ref #6
2020-12-18 14:29:31 +01:00
476afc6a99 Updated nameing to fit the usual scheme
ref #6
2020-12-18 14:29:17 +01:00
efecffb72d Added responseusers
ref #6
2020-12-17 21:12:45 +01:00
cc5a30980a Implemented new Permission system on the DB side.
ref #22
2020-12-17 20:46:54 +01:00
e3a5b41b5e Merge pull request 'latest work' (#20) from dev into main
Reviewed-on: #20
Reviewed-by: Nicolai Ort <info@nicolai-ort.com>
2020-12-09 18:49:30 +00:00
226 changed files with 28720 additions and 1726 deletions

View File

@@ -11,7 +11,7 @@ steps:
- git checkout $DRONE_SOURCE_BRANCH
- mv .env.ci .env
- name: run tests
image: node:alpine
image: node:latest
commands:
- yarn
- yarn test:ci
@@ -23,8 +23,15 @@ trigger:
kind: pipeline
type: docker
name: build:dev
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone $DRONE_REMOTE_URL .
- git checkout dev
- name: build dev
image: plugins/docker
depends_on: [clone]
@@ -37,11 +44,41 @@ steps:
tags:
- dev
registry: registry.odit.services
when:
branch:
- dev
event:
- push
- name: run changelog export
depends_on: ["clone"]
image: node:latest
commands:
- npx auto-changelog --commit-limit false -p -u --hide-credit
- name: push new changelog to repo
depends_on: ["run changelog export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: 🧾New changelog file version [CI SKIP] [skip ci]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/backend.git
ssh_key:
from_secret: GITLAB_SSHKEY
- name: run full license export
depends_on: ["clone"]
image: node:14.15.1-alpine3.12
commands:
- yarn
- yarn licenses:export
- name: push new licenses file to repo
depends_on: ["run full license export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: 📖New license file version [CI SKIP] [skip ci]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/backend.git
skip_verify: true
ssh_key:
from_secret: GITLAB_SSHKEY
trigger:
branch:
@@ -53,11 +90,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
@@ -67,6 +113,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:
@@ -90,22 +145,20 @@ steps:
from_secret: DOCKER_REGISTRY_PASSWORD
repo: registry.odit.services/lfk/backend
tags:
- $DRONE_TAG
- '${DRONE_TAG}'
registry: registry.odit.services
- name: trigger lib build
depends_on: [clone]
image: plugins/downstream
- name: trigger node lib build
image: idcooldi/drone-webhook
settings:
server: https://ci.odit.services/
token:
urls: https://ci.odit.services/api/repos/lfk/lfk-client-node/builds?SOURCE_TAG=${DRONE_TAG}
bearer:
from_secret: BOT_DRONE_KEY
- name: trigger js lib build
image: idcooldi/drone-webhook
settings:
urls: https://ci.odit.services/api/repos/lfk/lfk-client-js/builds?SOURCE_TAG=${DRONE_TAG}
bearer:
from_secret: BOT_DRONE_KEY
fork: false
repositories:
- lfk/lib
params:
- SOURCE_TAG: $DRONE_TAG
trigger:
branch:
- main
event:
- tag
- tag

View File

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

View File

@@ -5,4 +5,5 @@ DB_PORT=bla
DB_USER=bla
DB_PASSWORD=bla
DB_NAME=bla
NODE_ENV=production
NODE_ENV=production
POSTALCODE_COUNTRYCODE=DE

6
.gitignore vendored
View File

@@ -132,5 +132,7 @@ build
*.sqlite
*.sqlite-jurnal
docs
lib
/docs
lib
/oss-attribution
*.tmp

1027
CHANGELOG.md Normal file

File diff suppressed because it is too large Load Diff

1296
licenses.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "@lfk/backend",
"version": "1.0.0",
"name": "@odit/lfk-backend",
"version": "0.2.0",
"main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend",
"author": {
@@ -22,58 +22,77 @@
],
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"argon2": "^0.27.0",
"@odit/class-validator-jsonschema": "2.1.1",
"argon2": "^0.27.1",
"body-parser": "^1.19.0",
"class-transformer": "^0.3.1",
"class-validator": "^0.12.2",
"class-validator-jsonschema": "^2.0.3",
"class-transformer": "0.3.1",
"class-validator": "^0.13.1",
"consola": "^2.15.0",
"cookie": "^0.4.1",
"cookie-parser": "^1.4.5",
"cors": "^2.8.5",
"csvtojson": "^2.0.10",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"libphonenumber-js": "^1.9.7",
"mysql": "^2.18.1",
"pg": "^8.5.1",
"reflect-metadata": "^0.1.13",
"routing-controllers": "^0.9.0-alpha.6",
"routing-controllers-openapi": "^2.1.0",
"sqlite3": "^5.0.0",
"swagger-ui-express": "^4.1.5",
"routing-controllers-openapi": "^2.2.0",
"sqlite3": "5.0.0",
"typeorm": "^0.2.29",
"typeorm-routing-controllers-extensions": "^0.2.0",
"typeorm-seeding": "^1.6.1",
"uuid": "^8.3.1"
"uuid": "^8.3.2",
"validator": "^13.5.2"
},
"devDependencies": {
"@types/cors": "^2.8.8",
"@odit/license-exporter": "^0.0.9",
"@types/cors": "^2.8.9",
"@types/csvtojson": "^1.1.5",
"@types/express": "^4.17.9",
"@types/jest": "^26.0.16",
"@types/jsonwebtoken": "^8.5.0",
"@types/node": "^14.14.9",
"@types/swagger-ui-express": "^4.1.2",
"@types/node": "^14.14.20",
"@types/uuid": "^8.3.0",
"axios": "^0.21.0",
"axios": "^0.21.1",
"cp-cli": "^2.0.0",
"jest": "^26.6.3",
"nodemon": "^2.0.6",
"rimraf": "^2.7.1",
"start-server-and-test": "^1.11.6",
"nodemon": "^2.0.7",
"release-it": "^14.2.2",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.11.7",
"ts-jest": "^26.4.4",
"ts-node": "^9.0.0",
"typedoc": "^0.19.2",
"typescript": "^4.1.2"
"ts-node": "^9.1.1",
"typedoc": "^0.20.14",
"typescript": "^4.1.3"
},
"scripts": {
"dev": "nodemon src/app.ts",
"build": "tsc",
"build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static",
"docs": "typedoc --out docs src",
"test": "jest",
"test:watch": "jest --watchAll",
"test:ci": "start-server-and-test dev http://localhost:4010/api/openapi.json test",
"test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
"seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
"openapi:export": "ts-node src/openapi_export.ts"
"openapi:export": "ts-node scripts/openapi_export.ts",
"licenses:export": "license-exporter --md",
"release": "release-it --only-version"
},
"release-it": {
"git": {
"commit": true,
"requireCleanWorkingDir": false,
"commitMessage": "🚀Bumped version to v${version}",
"requireBranch": "dev",
"push": false,
"tag": false
},
"npm": {
"publish": false
}
},
"nodemonConfig": {
"ignore": [
@@ -81,4 +100,4 @@
"docs/*"
]
}
}
}

34
scripts/openapi_export.ts Normal file
View File

@@ -0,0 +1,34 @@
import { validationMetadatasToSchemas } from '@odit/class-validator-jsonschema';
import consola from "consola";
import fs from "fs";
import "reflect-metadata";
import { createExpressServer, getMetadataArgsStorage } from "routing-controllers";
import { generateSpec } from '../src/apispec';
import { config } from '../src/config';
import authchecker from "../src/middlewares/authchecker";
import { ErrorHandler } from '../src/middlewares/ErrorHandler';
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
createExpressServer({
authorizationChecker: authchecker,
middlewares: [ErrorHandler],
development: config.development,
cors: true,
routePrefix: "/api",
controllers: [`${__dirname}/../src/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
});
const storage = getMetadataArgsStorage();
const schemas = validationMetadatasToSchemas({
refPointerPrefix: "#/components/schemas/",
});
//Spec creation based on the previously created schemas
const spec = generateSpec(storage, schemas);
try {
fs.writeFileSync("./openapi.json", JSON.stringify(spec), { encoding: "utf-8" });
consola.success("Exported openapi spec to openapi.json");
} catch (error) {
consola.error("Couldn't export the openapi spec");
}

51
src/apispec.ts Normal file
View File

@@ -0,0 +1,51 @@
import { MetadataArgsStorage } from 'routing-controllers';
import { routingControllersToSpec } from 'routing-controllers-openapi';
import { config } from './config';
/**
* This function generates a the openapi spec from route metadata and type schemas.
* @param storage MetadataArgsStorage object generated by routing-controllers.
* @param schemas MetadataArgsStorage object generated by class-validator-jsonschema.
*/
export function generateSpec(storage: MetadataArgsStorage, schemas) {
return routingControllersToSpec(
storage,
{
routePrefix: "/api"
},
{
components: {
schemas,
"securitySchemes": {
"AuthToken": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT",
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
},
"RefreshTokenCookie": {
"type": "apiKey",
"in": "cookie",
"name": "lfk_backend__refresh_token",
description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
},
"StatsApiToken": {
"type": "http",
"scheme": "bearer",
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients). Only valid for obtaining stats."
},
"StationApiToken": {
"type": "http",
"scheme": "bearer",
description: "Api token that can be obtained by creating a new scan station (post to /api/stations). Only valid for creating scans."
}
}
},
info: {
description: "The the backend API for the LfK! runner system.",
title: "LfK! Backend API",
version: config.version
},
}
);
}

View File

@@ -1,14 +1,16 @@
import consola from "consola";
import "reflect-metadata";
import { createExpressServer } from "routing-controllers";
import authchecker from "./authchecker";
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

@@ -1,51 +0,0 @@
import * as jwt from "jsonwebtoken";
import { Action } from "routing-controllers";
import { getConnectionManager } from 'typeorm';
import { config } from './config';
import { IllegalJWTError, NoPermissionError, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError';
import { User } from './models/entities/User';
// -----------
const authchecker = async (action: Action, permissions: string | string[]) => {
let required_permissions = undefined
if (typeof permissions === "string") {
required_permissions = [permissions]
} else {
required_permissions = permissions
}
// const token = action.request.headers["authorization"];
const provided_token = action.request.query["auth"];
let jwtPayload = undefined
try {
jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret);
} catch (error) {
throw new IllegalJWTError()
}
const count = await getConnectionManager().get().getRepository(User).count({ id: jwtPayload["userdetails"]["id"], refreshTokenCount: jwtPayload["userdetails"]["refreshTokenCount"] })
if (count !== 1) {
throw new UserNonexistantOrRefreshtokenInvalidError()
}
if (jwtPayload.permissions) {
action.response.local = {}
action.response.local.jwtPayload = jwtPayload.permissions
required_permissions.forEach(r => {
const permission_key = r.split(":")[0]
const actual_accesslevel_for_permission = jwtPayload.permissions[permission_key]
const permission_access_level = r.split(":")[1]
if (actual_accesslevel_for_permission.includes(permission_access_level)) {
return true;
} else {
throw new NoPermissionError()
}
});
} else {
throw new NoPermissionError()
}
//
try {
jwt.verify(provided_token, config.jwt_secret);
return true
} catch (error) {
return false
}
}
export default authchecker

View File

@@ -1,22 +1,33 @@
import { config as configDotenv } from 'dotenv';
import { CountryCode } from 'libphonenumber-js';
import ValidatorJS from 'validator';
configDotenv();
export const config = {
internal_port: parseInt(process.env.APP_PORT) || 4010,
development: process.env.NODE_ENV === "production",
jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ"
phone_validation_countrycode: getPhoneCodeLocale(),
postalcode_validation_countrycode: getPostalCodeLocale(),
version: process.env.VERSION || require('../package.json').version
}
let errors = 0
if (typeof config.internal_port !== "number") {
errors++
}
if (typeof config.phone_validation_countrycode !== "string") {
errors++
}
if (config.phone_validation_countrycode.length !== 2) {
errors++
}
if (typeof config.development !== "boolean") {
errors++
}
function getPhoneCodeLocale(): CountryCode {
return (process.env.PHONE_COUNTRYCODE as CountryCode);
}
function getPostalCodeLocale(): any {
try {
const stringArray: String[] = ValidatorJS.isPostalCodeLocales;
let index = stringArray.indexOf(process.env.POSTALCODE_COUNTRYCODE);
return ValidatorJS.isPostalCodeLocales[index];
} catch (error) {
return null;
}
}
export let e = errors

View File

@@ -1,11 +1,13 @@
import { Body, CookieParam, JsonController, Post, Res } from 'routing-controllers';
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
import { UserNotFoundError } from '../errors/UserErrors';
import { CreateAuth } from '../models/actions/CreateAuth';
import { 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 { Auth } from '../models/responses/ResponseAuth';
import { ResetPassword } from '../models/actions/ResetPassword';
import { ResponseAuth } from '../models/responses/ResponseAuth';
import { Logout } from '../models/responses/ResponseLogout';
@JsonController('/auth')
@@ -14,13 +16,13 @@ export class AuthController {
}
@Post("/login")
@ResponseSchema(Auth)
@ResponseSchema(ResponseAuth)
@ResponseSchema(InvalidCredentialsError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Create a new access token object' })
@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
let auth;
try {
@@ -40,7 +42,7 @@ export class AuthController {
@ResponseSchema(UsernameOrEmailNeededError)
@ResponseSchema(PasswordNeededError)
@ResponseSchema(InvalidCredentialsError)
@OpenAPI({ description: 'Create a new access token object' })
@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
handleLogout.token = refresh_token;
@@ -58,13 +60,13 @@ export class AuthController {
}
@Post("/refresh")
@ResponseSchema(Auth)
@ResponseSchema(ResponseAuth)
@ResponseSchema(JwtNotProvidedError)
@ResponseSchema(IllegalJWTError)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(RefreshTokenCountInvalidError)
@OpenAPI({ description: 'refresh a access token' })
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
refreshAuth.token = refresh_token;
}
@@ -78,4 +80,24 @@ export class AuthController {
}
return response.send(auth)
}
@Post("/reset")
@ResponseSchema(ResponseAuth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) {
//This really shouldn't just get returned, but sent via mail or sth like that. But for dev only this is fine.
return { "resetToken": await passwordReset.toResetToken() };
}
@Post("/reset/:token")
@ResponseSchema(ResponseAuth)
@ResponseSchema(UserNotFoundError)
@ResponseSchema(UsernameOrEmailNeededError)
@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
passwordReset.resetToken = token;
return await passwordReset.resetPassword();
}
}

View File

@@ -0,0 +1,145 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors';
import { DonorNotFoundError } from '../errors/DonorErrors';
import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation';
import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation';
import { UpdateDistanceDonation } from '../models/actions/update/UpdateDistanceDonation';
import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonation';
import { DistanceDonation } from '../models/entities/DistanceDonation';
import { Donation } from '../models/entities/Donation';
import { FixedDonation } from '../models/entities/FixedDonation';
import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation';
import { ResponseDonation } from '../models/responses/ResponseDonation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
@JsonController('/donations')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class DonationController {
private donationRepository: Repository<Donation>;
private distanceDonationRepository: Repository<DistanceDonation>;
private fixedDonationRepository: Repository<FixedDonation>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.donationRepository = getConnectionManager().get().getRepository(Donation);
this.distanceDonationRepository = getConnectionManager().get().getRepository(DistanceDonation);
this.fixedDonationRepository = getConnectionManager().get().getRepository(FixedDonation);
}
@Get()
@Authorized("DONATION:GET")
@ResponseSchema(ResponseDonation, { isArray: true })
@ResponseSchema(ResponseDistanceDonation, { isArray: true })
@OpenAPI({ description: 'Lists all donations (fixed or distance based) from all donors. <br> This includes the donations\'s runner\'s distance ran(if distance donation).' })
async getAll() {
let responseDonations: ResponseDonation[] = new Array<ResponseDonation>();
const donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
donations.forEach(donation => {
responseDonations.push(donation.toResponse());
});
return responseDonations;
}
@Get('/:id')
@Authorized("DONATION:GET")
@ResponseSchema(ResponseDonation)
@ResponseSchema(ResponseDistanceDonation)
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
@OnUndefined(DonationNotFoundError)
@OpenAPI({ description: 'Lists all information about the donation whose id got provided. This includes the donation\'s runner\'s distance ran (if distance donation).' })
async getOne(@Param('id') id: number) {
let donation = await this.donationRepository.findOne({ id: id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })
if (!donation) { throw new DonationNotFoundError(); }
return donation.toResponse();
}
@Post('/fixed')
@Authorized("DONATION:CREATE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a fixed donation (not distance donation - use /donations/distance instead). <br> Please rmemember to provide the donation\'s donors\'s id and amount.' })
async postFixed(@Body({ validate: true }) createDonation: CreateFixedDonation) {
let donation = await createDonation.toEntity();
donation = await this.fixedDonationRepository.save(donation);
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
}
@Post('/distance')
@Authorized("DONATION:CREATE")
@ResponseSchema(ResponseDistanceDonation)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a distance donation (not fixed donation - use /donations/fixed instead). <br> Please rmemember to provide the donation\'s donors\'s and runner\s ids and amount per distance (kilometer).' })
async postDistance(@Body({ validate: true }) createDonation: CreateDistanceDonation) {
let donation = await createDonation.toEntity();
donation = await this.distanceDonationRepository.save(donation);
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
}
@Put('/fixed/:id')
@Authorized("DONATION:UPDATE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/distance instead) whose id you provided. <br> Please remember that ids can't be changed and amounts must be positive." })
async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateFixedDonation) {
let oldDonation = await this.fixedDonationRepository.findOne({ id: id });
if (!oldDonation) {
throw new DonationNotFoundError();
}
if (oldDonation.id != donation.id) {
throw new DonationIdsNotMatchingError();
}
await this.fixedDonationRepository.save(await donation.update(oldDonation));
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
}
@Put('/distance/:id')
@Authorized("DONATION:UPDATE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the distance donation (not fixed donation - use /donations/fixed instead) whose id you provided. <br> Please remember that ids can't be changed and amountPerDistance must be positive." })
async putDistance(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) {
let oldDonation = await this.distanceDonationRepository.findOne({ id: id });
if (!oldDonation) {
throw new DonationNotFoundError();
}
if (oldDonation.id != donation.id) {
throw new DonationIdsNotMatchingError();
}
await this.distanceDonationRepository.save(await donation.update(oldDonation));
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
}
@Delete('/:id')
@Authorized("DONATION:DELETE")
@ResponseSchema(ResponseDonation)
@ResponseSchema(ResponseDistanceDonation)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the donation whose id you provided. <br> If no donation with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let donation = await this.donationRepository.findOne({ id: id });
if (!donation) { return null; }
const responseScan = await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
await this.donationRepository.delete(donation);
return responseScan.toResponse();
}
}

View File

@@ -0,0 +1,113 @@
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 { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
import { CreateDonor } from '../models/actions/create/CreateDonor';
import { UpdateDonor } from '../models/actions/update/UpdateDonor';
import { Donor } from '../models/entities/Donor';
import { ResponseDonor } from '../models/responses/ResponseDonor';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { DonationController } from './DonationController';
@JsonController('/donors')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class DonorController {
private donorRepository: Repository<Donor>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.donorRepository = getConnectionManager().get().getRepository(Donor);
}
@Get()
@Authorized("DONOR:GET")
@ResponseSchema(ResponseDonor, { isArray: true })
@OpenAPI({ description: 'Lists all donor. <br> This includes the donor\'s current donation amount.' })
async getAll() {
let responseDonors: ResponseDonor[] = new Array<ResponseDonor>();
const donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
donors.forEach(donor => {
responseDonors.push(new ResponseDonor(donor));
});
return responseDonors;
}
@Get('/:id')
@Authorized("DONOR:GET")
@ResponseSchema(ResponseDonor)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@OnUndefined(DonorNotFoundError)
@OpenAPI({ description: 'Lists all information about the donor whose id got provided. <br> This includes the donor\'s current donation amount.' })
async getOne(@Param('id') id: number) {
let donor = await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })
if (!donor) { throw new DonorNotFoundError(); }
return new ResponseDonor(donor);
}
@Post()
@Authorized("DONOR:CREATE")
@ResponseSchema(ResponseDonor)
@OpenAPI({ description: 'Create a new donor.' })
async post(@Body({ validate: true }) createRunner: CreateDonor) {
let donor;
try {
donor = await createRunner.toEntity();
} catch (error) {
throw error;
}
donor = await this.donorRepository.save(donor)
return new ResponseDonor(await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
}
@Put('/:id')
@Authorized("DONOR:UPDATE")
@ResponseSchema(ResponseDonor)
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
@ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the donor whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) {
let oldDonor = await this.donorRepository.findOne({ id: id });
if (!oldDonor) {
throw new DonorNotFoundError();
}
if (oldDonor.id != donor.id) {
throw new DonorIdsNotMatchingError();
}
await this.donorRepository.save(await donor.update(oldDonor));
return new ResponseDonor(await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
}
@Delete('/:id')
@Authorized("DONOR:DELETE")
@ResponseSchema(ResponseDonor)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the donor whose id you provided. <br> If no donor with this id exists it will just return 204(no content). <br> If the donor still has donations associated this will fail, please provide the query param ?force=true to delete the donor with all associated donations.' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let donor = await this.donorRepository.findOne({ id: id });
if (!donor) { return null; }
const responseDonor = await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
if (!donor) {
throw new DonorNotFoundError();
}
const donorDonations = (await this.donorRepository.findOne({ id: donor.id }, { relations: ["donations"] })).donations;
if (donorDonations.length > 0 && !force) {
throw new DonorHasDonationsError();
}
const donationController = new DonationController();
for (let donation of donorDonations) {
await donationController.remove(donation.id, force);
}
await this.donorRepository.delete(donor);
return new ResponseDonor(responseDonor);
}
}

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'] });
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'] })
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'] })).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'] })).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'] });
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

@@ -1,5 +1,5 @@
import csv from 'csvtojson';
import { Body, ContentType, Controller, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
import { Authorized, Body, ContentType, Controller, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { RunnerGroupNeededError } from '../errors/RunnerErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
@@ -9,7 +9,8 @@ import { ResponseRunner } from '../models/responses/ResponseRunner';
import { RunnerController } from './RunnerController';
@Controller()
//@Authorized("IMPORT:read")
@Authorized(["RUNNER:IMPORT", "TEAM:IMPORT"])
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ImportController {
private runnerController: RunnerController;
@@ -25,7 +26,7 @@ export class ImportController {
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
@OpenAPI({ description: "Create new runners from json and insert them (or their teams) into the provided group" })
@OpenAPI({ description: "Create new runners from json and insert them into the provided group. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
async postJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @QueryParam("group") groupID: number) {
if (!groupID) { throw new RunnerGroupNeededError(); }
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
@@ -40,7 +41,7 @@ export class ImportController {
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
@OpenAPI({ description: "Create new runners from json and insert them (or their teams) into the provided org" })
@OpenAPI({ description: "Create new runners from json and insert them into the provided org. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
async postOrgsJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @Param('id') id: number) {
return await this.postJSON(importRunners, id)
}
@@ -61,7 +62,7 @@ export class ImportController {
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
@OpenAPI({ description: "Create new runners from csv and insert them (or their teams) into the provided group" })
@OpenAPI({ description: "Create new runners from csv and insert them into the provided group. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
async postCSV(@Req() request: any, @QueryParam("group") groupID: number) {
let csvParse = await csv({ delimiter: [",", ";"], trim: true }).fromString(request.rawBody.toString());
let importRunners: ImportRunner[] = new Array<ImportRunner>();
@@ -83,7 +84,7 @@ export class ImportController {
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
@OpenAPI({ description: "Create new runners from csv and insert them (or their teams) into the provided org" })
@OpenAPI({ description: "Create new runners from csv and insert them into the provided org. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
async postOrgsCSV(@Req() request: any, @Param("id") id: number) {
return await this.postCSV(request, id);
}

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

@@ -0,0 +1,118 @@
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 { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors';
import { PrincipalNotFoundError } from '../errors/PrincipalErrors';
import { CreatePermission } from '../models/actions/create/CreatePermission';
import { UpdatePermission } from '../models/actions/update/UpdatePermission';
import { Permission } from '../models/entities/Permission';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponsePermission } from '../models/responses/ResponsePermission';
import { ResponsePrincipal } from '../models/responses/ResponsePrincipal';
@JsonController('/permissions')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class PermissionController {
private permissionRepository: Repository<Permission>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.permissionRepository = getConnectionManager().get().getRepository(Permission);
}
@Get()
@Authorized("PERMISSION:GET")
@ResponseSchema(ResponsePermission, { isArray: true })
@OpenAPI({ description: 'Lists all permissions for all users and groups.' })
async getAll() {
let responsePermissions: ResponsePermission[] = new Array<ResponsePermission>();
const permissions = await this.permissionRepository.find({ relations: ['principal'] });
permissions.forEach(permission => {
responsePermissions.push(new ResponsePermission(permission));
});
return responsePermissions;
}
@Get('/:id')
@Authorized("PERMISSION:GET")
@ResponseSchema(ResponsePermission)
@ResponseSchema(PermissionNotFoundError, { statusCode: 404 })
@OnUndefined(PermissionNotFoundError)
@OpenAPI({ description: 'Lists all information about the permission whose id got provided.' })
async getOne(@Param('id') id: number) {
let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] });
if (!permission) { throw new PermissionNotFoundError(); }
return new ResponsePermission(permission);
}
@Post()
@Authorized("PERMISSION:CREATE")
@ResponseSchema(ResponsePermission)
@ResponseSchema(PrincipalNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new permission for a existing principal(user/group). <br> If a permission with this target, action and prinicpal already exists that permission will be returned instead of creating a new one.' })
async post(@Body({ validate: true }) createPermission: CreatePermission) {
let permission;
try {
permission = await createPermission.toEntity();
} catch (error) {
throw error;
}
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] });
if (existingPermission) { return new ResponsePermission(existingPermission); }
permission = await this.permissionRepository.save(permission);
permission = await this.permissionRepository.findOne(permission, { relations: ['principal'] });
return new ResponsePermission(permission);
}
@Put('/:id')
@Authorized("PERMISSION:UPDATE")
@ResponseSchema(ResponsePrincipal)
@ResponseSchema(PermissionNotFoundError, { statusCode: 404 })
@ResponseSchema(PrincipalNotFoundError, { statusCode: 404 })
@ResponseSchema(PermissionIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(PermissionNeedsPrincipalError, { statusCode: 406 })
@OpenAPI({ description: "Update a permission object. <br> If updateing the permission object would result in duplicate permission (same target, action and principal) this permission will get deleted and the existing permission will be returned. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) permission: UpdatePermission) {
let oldPermission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] });
if (!oldPermission) {
throw new PermissionNotFoundError();
}
if (oldPermission.id != permission.id) {
throw new PermissionIdsNotMatchingError();
}
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: await permission.getPrincipal() }, { relations: ['principal'] });
if (existingPermission) {
await this.remove(permission.id, true);
return new ResponsePermission(existingPermission);
}
await this.permissionRepository.save(await permission.update(oldPermission));
return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
}
@Delete('/:id')
@Authorized("PERMISSION:DELETE")
@ResponseSchema(ResponsePermission)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Deletes the permission whose id you provide. <br> If no permission with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] });
if (!permission) { return null; }
const responsePermission = new ResponsePermission(permission);
await this.permissionRepository.delete(permission);
return responsePermission;
}
}

View File

@@ -0,0 +1,106 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors';
import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard';
import { UpdateRunnerCard } from '../models/actions/update/UpdateRunnerCard';
import { RunnerCard } from '../models/entities/RunnerCard';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard';
import { ScanController } from './ScanController';
@JsonController('/cards')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerCardController {
private cardRepository: Repository<RunnerCard>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.cardRepository = getConnectionManager().get().getRepository(RunnerCard);
}
@Get()
@Authorized("CARD:GET")
@ResponseSchema(ResponseRunnerCard, { isArray: true })
@OpenAPI({ description: 'Lists all card.' })
async getAll() {
let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>();
const cards = await this.cardRepository.find({ relations: ['runner'] });
cards.forEach(card => {
responseCards.push(new ResponseRunnerCard(card));
});
return responseCards;
}
@Get('/:id')
@Authorized("CARD:GET")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerCardNotFoundError)
@OpenAPI({ description: "Lists all information about the card whose id got provided." })
async getOne(@Param('id') id: number) {
let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner'] });
if (!card) { throw new RunnerCardNotFoundError(); }
return card.toResponse();
}
@Post()
@Authorized("CARD:CREATE")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: "Create a new card. <br> You can provide a associated runner by id but you don't have to." })
async post(@Body({ validate: true }) createCard: CreateRunnerCard) {
let card = await createCard.toEntity();
card = await this.cardRepository.save(card);
return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner'] })).toResponse();
}
@Put('/:id')
@Authorized("CARD:UPDATE")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the card whose id you provided. <br> Scans created via this card will still be associated with the old runner. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) {
let oldCard = await this.cardRepository.findOne({ id: id });
if (!oldCard) {
throw new RunnerCardNotFoundError();
}
if (oldCard.id != card.id) {
throw new RunnerCardIdsNotMatchingError();
}
await this.cardRepository.save(await card.update(oldCard));
return (await this.cardRepository.findOne({ id: id }, { relations: ['runner'] })).toResponse();
}
@Delete('/:id')
@Authorized("CARD:DELETE")
@ResponseSchema(ResponseRunnerCard)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerCardHasScansError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: "Delete the card whose id you provided. <br> If no card with this id exists it will just return 204(no content). <br> If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let card = await this.cardRepository.findOne({ id: id });
if (!card) { return null; }
const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans;
if (cardScans.length != 0 && !force) {
throw new RunnerCardHasScansError();
}
const scanController = new ScanController;
for (let scan of cardScans) {
await scanController.remove(scan.id, force);
}
await this.cardRepository.delete(card);
return card.toResponse();
}
}

View File

@@ -1,16 +1,19 @@
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { CreateRunner } from '../models/actions/CreateRunner';
import { UpdateRunner } from '../models/actions/UpdateRunner';
import { CreateRunner } from '../models/actions/create/CreateRunner';
import { UpdateRunner } from '../models/actions/update/UpdateRunner';
import { Runner } from '../models/entities/Runner';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunner } from '../models/responses/ResponseRunner';
import { DonationController } from './DonationController';
import { RunnerCardController } from './RunnerCardController';
import { ScanController } from './ScanController';
@JsonController('/runners')
//@Authorized('RUNNERS:read')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerController {
private runnerRepository: Repository<Runner>;
@@ -22,11 +25,12 @@ export class RunnerController {
}
@Get()
@Authorized("RUNNER:GET")
@ResponseSchema(ResponseRunner, { isArray: true })
@OpenAPI({ description: 'Lists all runners.' })
@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
async getAll() {
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] });
const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'scans.track', 'cards'] });
runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner));
});
@@ -34,38 +38,41 @@ export class RunnerController {
}
@Get('/:id')
@Authorized("RUNNER:GET")
@ResponseSchema(ResponseRunner)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Returns a runner of a specified id (if it exists)' })
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
async getOne(@Param('id') id: number) {
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] })
if (!runner) { throw new RunnerNotFoundError(); }
return new ResponseRunner(runner);
}
@Post()
@Authorized("RUNNER:CREATE")
@ResponseSchema(ResponseRunner)
@ResponseSchema(RunnerGroupNeededError)
@ResponseSchema(RunnerGroupNotFoundError)
@OpenAPI({ description: 'Create a new runner object (id will be generated automagicly).' })
@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' })
async post(@Body({ validate: true }) createRunner: CreateRunner) {
let runner;
try {
runner = await createRunner.toRunner();
runner = await createRunner.toEntity();
} catch (error) {
throw error;
}
runner = await this.runnerRepository.save(runner)
return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }));
return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] }));
}
@Put('/:id')
@Authorized("RUNNER:UPDATE")
@ResponseSchema(ResponseRunner)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a runner object (id can't be changed)." })
@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) runner: UpdateRunner) {
let oldRunner = await this.runnerRepository.findOne({ id: id }, { relations: ['group'] });
@@ -77,24 +84,47 @@ export class RunnerController {
throw new RunnerIdsNotMatchingError();
}
await this.runnerRepository.update(oldRunner, await runner.toRunner());
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
await this.runnerRepository.save(await runner.update(oldRunner));
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'scans.track', 'cards'] }));
}
@Delete('/:id')
@Authorized("RUNNER:DELETE")
@ResponseSchema(ResponseRunner)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerHasDistanceDonationsError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a specified runner (if it exists).' })
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> This will also delete all scans and cards associated with the runner. <br> If no runner with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let runner = await this.runnerRepository.findOne({ id: id });
if (!runner) { return null; }
const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] });
const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'scans.track', 'cards'] });
if (!runner) {
throw new RunnerNotFoundError();
}
const runnerDonations = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["distanceDonations"] })).distanceDonations;
if (runnerDonations.length > 0 && !force) {
throw new RunnerHasDistanceDonationsError();
}
const donationController = new DonationController();
for (let donation of runnerDonations) {
await donationController.remove(donation.id, force);
}
const runnerCards = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["cards"] })).cards;
const cardController = new RunnerCardController;
for (let card of runnerCards) {
await cardController.remove(card.id, force);
}
const runnerScans = (await this.runnerRepository.findOne({ id: runner.id }, { relations: ["scans"] })).scans;
const scanController = new ScanController;
for (let scan of runnerScans) {
await scanController.remove(scan.id, force);
}
await this.runnerRepository.delete(runner);
return new ResponseRunner(responseRunner);
}

View File

@@ -1,9 +1,9 @@
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
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';
@@ -12,7 +12,7 @@ import { RunnerTeamController } from './RunnerTeamController';
@JsonController('/organisations')
//@Authorized('RUNNERS:read')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerOrganisationController {
private runnerOrganisationRepository: Repository<RunnerOrganisation>;
@@ -24,11 +24,12 @@ export class RunnerOrganisationController {
}
@Get()
@Authorized("ORGANISATION:GET")
@ResponseSchema(ResponseRunnerOrganisation, { isArray: true })
@OpenAPI({ description: 'Lists all runnerOrganisations.' })
@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'] });
const runners = await this.runnerOrganisationRepository.find({ relations: ['contact', 'teams'] });
runners.forEach(runner => {
responseTeams.push(new ResponseRunnerOrganisation(runner));
});
@@ -36,65 +37,68 @@ export class RunnerOrganisationController {
}
@Get('/:id')
@Authorized("ORGANISATION:GET")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerOrganisationNotFoundError)
@OpenAPI({ description: 'Returns a runnerOrganisation of a specified id (if it exists)' })
@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'] });
let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['contact', 'teams'] });
if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); }
return new ResponseRunnerOrganisation(runnerOrg);
}
@Post()
@Authorized("ORGANISATION:CREATE")
@ResponseSchema(ResponseRunnerOrganisation)
@OpenAPI({ description: 'Create a new runnerOrganisation object (id will be generated automagicly).' })
@OpenAPI({ description: 'Create a new organsisation.' })
async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) {
let runnerOrganisation;
try {
runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation();
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'] }));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['contact', 'teams'] }));
}
@Put('/:id')
@Authorized("ORGANISATION:UPDATE")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a runnerOrganisation object (id can't be changed)." })
async put(@Param('id') id: number, @EntityFromBody() runnerOrganisation: RunnerOrganisation) {
@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 != runnerOrganisation.id) {
if (oldRunnerOrganisation.id != updateOrganisation.id) {
throw new RunnerOrganisationIdsNotMatchingError();
}
await this.runnerOrganisationRepository.update(oldRunnerOrganisation, runnerOrganisation);
await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation));
runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] });
return new ResponseRunnerOrganisation(runnerOrganisation);
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['contact', 'teams'] }));
}
@Delete('/:id')
@Authorized("ORGANISATION:DELETE")
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a specified runnerOrganisation (if it exists).' })
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organisation still has runners and/or teams associated this will fail. <br> To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact. <br> If no organisation with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
if (!organisation) { return null; }
let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] });
let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['contact', 'runners', 'teams'] });
if (!force) {
if (runnerOrganisation.teams.length != 0) {

View File

@@ -1,9 +1,9 @@
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
import { UpdateRunnerTeam } from '../models/actions/UpdateRunnerTeam';
import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam';
import { RunnerTeam } from '../models/entities/RunnerTeam';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
@@ -11,7 +11,7 @@ import { RunnerController } from './RunnerController';
@JsonController('/teams')
//@Authorized('RUNNERS:read')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerTeamController {
private runnerTeamRepository: Repository<RunnerTeam>;
@@ -23,8 +23,9 @@ export class RunnerTeamController {
}
@Get()
@Authorized("TEAM:GET")
@ResponseSchema(ResponseRunnerTeam, { isArray: true })
@OpenAPI({ description: 'Lists all runnerTeams.' })
@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organisation and contact (if existing/associated).' })
async getAll() {
let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>();
const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
@@ -35,10 +36,11 @@ export class RunnerTeamController {
}
@Get('/:id')
@Authorized("TEAM:GET")
@ResponseSchema(ResponseRunnerTeam)
@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerTeamNotFoundError)
@OpenAPI({ description: 'Returns a runnerTeam of a specified id (if it exists)' })
@OpenAPI({ description: 'Lists all information about the team whose id got provided.' })
async getOne(@Param('id') id: number) {
let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
if (!runnerTeam) { throw new RunnerTeamNotFoundError(); }
@@ -46,12 +48,13 @@ export class RunnerTeamController {
}
@Post()
@Authorized("TEAM:CREATE")
@ResponseSchema(ResponseRunnerTeam)
@OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' })
@OpenAPI({ description: 'Create a new organsisation. <br> Please remember to provide it\'s parent group\'s id.' })
async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
let runnerTeam;
try {
runnerTeam = await createRunnerTeam.toRunnerTeam();
runnerTeam = await createRunnerTeam.toEntity();
} catch (error) {
throw error;
}
@@ -63,10 +66,11 @@ export class RunnerTeamController {
}
@Put('/:id')
@Authorized("TEAM:UPDATE")
@ResponseSchema(ResponseRunnerTeam)
@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." })
@OpenAPI({ description: "Update the team whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) runnerTeam: UpdateRunnerTeam) {
let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
@@ -78,17 +82,18 @@ export class RunnerTeamController {
throw new RunnerTeamIdsNotMatchingError();
}
await this.runnerTeamRepository.update(oldRunnerTeam, await runnerTeam.toRunnerTeam());
await this.runnerTeamRepository.save(await runnerTeam.update(oldRunnerTeam));
return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
}
@Delete('/:id')
@Authorized("TEAM:DELETE")
@ResponseSchema(ResponseRunnerTeam)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a specified runnerTeam (if it exists).' })
@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

@@ -0,0 +1,140 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors';
import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
import ScanAuth from '../middlewares/ScanAuth';
import { CreateScan } from '../models/actions/create/CreateScan';
import { CreateTrackScan } from '../models/actions/create/CreateTrackScan';
import { UpdateScan } from '../models/actions/update/UpdateScan';
import { UpdateTrackScan } from '../models/actions/update/UpdateTrackScan';
import { Scan } from '../models/entities/Scan';
import { TrackScan } from '../models/entities/TrackScan';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScan } from '../models/responses/ResponseScan';
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
@JsonController('/scans')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ScanController {
private scanRepository: Repository<Scan>;
private trackScanRepository: Repository<TrackScan>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.scanRepository = getConnectionManager().get().getRepository(Scan);
this.trackScanRepository = getConnectionManager().get().getRepository(TrackScan);
}
@Get()
@Authorized("SCAN:GET")
@ResponseSchema(ResponseScan, { isArray: true })
@ResponseSchema(ResponseTrackScan, { isArray: true })
@OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' })
async getAll() {
let responseScans: ResponseScan[] = new Array<ResponseScan>();
const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] });
scans.forEach(scan => {
responseScans.push(scan.toResponse());
});
return responseScans;
}
@Get('/:id')
@Authorized("SCAN:GET")
@ResponseSchema(ResponseScan)
@ResponseSchema(ResponseTrackScan)
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
@OnUndefined(ScanNotFoundError)
@OpenAPI({ description: 'Lists all information about the scan whose id got provided. This includes the scan\'s runner\'s distance ran.' })
async getOne(@Param('id') id: number) {
let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })
if (!scan) { throw new ScanNotFoundError(); }
return scan.toResponse();
}
@Post()
@UseBefore(ScanAuth)
@ResponseSchema(ResponseScan)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new scan (not track scan - use /scans/trackscans instead). <br> Please rmemember to provide the scan\'s runner\'s id and distance.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async post(@Body({ validate: true }) createScan: CreateScan) {
let scan = await createScan.toEntity();
scan = await this.scanRepository.save(scan);
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Post("/trackscans")
@UseBefore(ScanAuth)
@ResponseSchema(ResponseTrackScan)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new track scan (for "normal" scans use /scans instead). <br> Please remember that to provide the scan\'s card\'s station\'s id.', security: [{ "ScanApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
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();
}
@Put('/:id')
@Authorized("SCAN:UPDATE")
@ResponseSchema(ResponseScan)
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the scan (not track scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that ids can't be changed and distances must be positive." })
async put(@Param('id') id: number, @Body({ validate: true }) scan: UpdateScan) {
let oldScan = await this.scanRepository.findOne({ id: id });
if (!oldScan) {
throw new ScanNotFoundError();
}
if (oldScan.id != scan.id) {
throw new ScanIdsNotMatchingError();
}
await this.scanRepository.save(await scan.update(oldScan));
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Put('/trackscans/:id')
@Authorized("SCAN:UPDATE")
@ResponseSchema(ResponseTrackScan)
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: 'Update the track scan (not "normal" scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that only the validity, runner and track can be changed.' })
async putTrackScan(@Param('id') id: number, @Body({ validate: true }) scan: UpdateTrackScan) {
let oldScan = await this.trackScanRepository.findOne({ id: id });
if (!oldScan) {
throw new ScanNotFoundError();
}
if (oldScan.id != scan.id) {
throw new ScanIdsNotMatchingError();
}
await this.trackScanRepository.save(await scan.update(oldScan));
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] })).toResponse();
}
@Delete('/:id')
@Authorized("SCAN:DELETE")
@ResponseSchema(ResponseScan)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the scan whose id you provided. <br> If no scan with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let scan = await this.scanRepository.findOne({ id: id });
if (!scan) { return null; }
const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.scans.track', 'card', 'station'] });
await this.scanRepository.delete(scan);
return responseScan.toResponse();
}
}

View File

@@ -0,0 +1,108 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { TrackNotFoundError } from '../errors/TrackErrors';
import { CreateScanStation } from '../models/actions/create/CreateScanStation';
import { UpdateScanStation } from '../models/actions/update/UpdateScanStation';
import { ScanStation } from '../models/entities/ScanStation';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
import { ScanController } from './ScanController';
@JsonController('/stations')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ScanStationController {
private stationRepository: Repository<ScanStation>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.stationRepository = getConnectionManager().get().getRepository(ScanStation);
}
@Get()
@Authorized("STATION:GET")
@ResponseSchema(ResponseScanStation, { isArray: true })
@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' })
async getAll() {
let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>();
const stations = await this.stationRepository.find({ relations: ['track'] });
stations.forEach(station => {
responseStations.push(station.toResponse());
});
return responseStations;
}
@Get('/:id')
@Authorized("STATION:GET")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@OnUndefined(ScanStationNotFoundError)
@OpenAPI({ description: 'Lists all information about the station whose id got provided. <br> This includes it\'s associated track.' })
async getOne(@Param('id') id: number) {
let scan = await this.stationRepository.findOne({ id: id }, { relations: ['track'] })
if (!scan) { throw new ScanStationNotFoundError(); }
return scan.toResponse();
}
@Post()
@Authorized("STATION:CREATE")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new station. <br> Please remeber to provide the station\'s track\'s id. <br> Please also remember that the station key is only visibe on creation.' })
async post(@Body({ validate: true }) createStation: CreateScanStation) {
let newStation = await createStation.toEntity();
const station = await this.stationRepository.save(newStation);
let responseStation = (await this.stationRepository.findOne({ id: station.id }, { relations: ['track'] })).toResponse();
responseStation.key = newStation.cleartextkey;
return responseStation;
}
@Put('/:id')
@Authorized("STATION:UPDATE")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
@ResponseSchema(ScanStationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update the station whose id you provided. <br> Please remember that only the description and enabled state can be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) station: UpdateScanStation) {
let oldStation = await this.stationRepository.findOne({ id: id });
if (!oldStation) {
throw new ScanStationNotFoundError();
}
if (oldStation.id != station.id) {
throw new ScanStationIdsNotMatchingError();
}
await this.stationRepository.save(await station.update(oldStation));
return (await this.stationRepository.findOne({ id: id }, { relations: ['track'] })).toResponse();
}
@Delete('/:id')
@Authorized("STATION:DELETE")
@ResponseSchema(ResponseScanStation)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(ScanStationHasScansError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete the station whose id you provided. <br> If no station with this id exists it will just return 204(no content). <br> If the station still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with/created by this station - please disable it instead).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let station = await this.stationRepository.findOne({ id: id });
if (!station) { return null; }
const stationScans = (await this.stationRepository.findOne({ id: station.id }, { relations: ["scans"] })).scans;
if (stationScans.length != 0 && !force) {
throw new ScanStationHasScansError();
}
const scanController = new ScanController;
for (let scan of stationScans) {
await scanController.remove(scan.id, force);
}
const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] });
await this.stationRepository.delete(station);
return responseStation.toResponse();
}
}

View File

@@ -0,0 +1,75 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { StatsClientNotFoundError } from '../errors/StatsClientErrors';
import { TrackNotFoundError } from "../errors/TrackErrors";
import { CreateStatsClient } from '../models/actions/create/CreateStatsClient';
import { StatsClient } from '../models/entities/StatsClient';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseStatsClient } from '../models/responses/ResponseStatsClient';
@JsonController('/statsclients')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class StatsClientController {
private clientRepository: Repository<StatsClient>;
/**
* Gets the repository of this controller's model/entity.
*/
constructor() {
this.clientRepository = getConnectionManager().get().getRepository(StatsClient);
}
@Get()
@Authorized("STATSCLIENT:GET")
@ResponseSchema(ResponseStatsClient, { isArray: true })
@OpenAPI({ description: 'Lists all stats clients. Please remember that the key can only be viewed on creation.' })
async getAll() {
let responseClients: ResponseStatsClient[] = new Array<ResponseStatsClient>();
const clients = await this.clientRepository.find();
clients.forEach(clients => {
responseClients.push(new ResponseStatsClient(clients));
});
return responseClients;
}
@Get('/:id')
@Authorized("STATSCLIENT:GET")
@ResponseSchema(ResponseStatsClient)
@ResponseSchema(StatsClientNotFoundError, { statusCode: 404 })
@OnUndefined(StatsClientNotFoundError)
@OpenAPI({ description: "Lists all information about the stats client whose id got provided. Please remember that the key can only be viewed on creation." })
async getOne(@Param('id') id: number) {
let client = await this.clientRepository.findOne({ id: id });
if (!client) { throw new TrackNotFoundError(); }
return new ResponseStatsClient(client);
}
@Post()
@Authorized("STATSCLIENT:CREATE")
@ResponseSchema(ResponseStatsClient)
@OpenAPI({ description: "Create a new stats client. <br> Please remember that the client\'s key will be generated automaticly and that it can only be viewed on creation." })
async post(
@Body({ validate: true })
client: CreateStatsClient
) {
let newClient = await this.clientRepository.save(await client.toEntity());
let responseClient = new ResponseStatsClient(newClient);
responseClient.key = newClient.cleartextkey;
return responseClient;
}
@Delete('/:id')
@Authorized("STATSCLIENT:DELETE")
@ResponseSchema(ResponseStatsClient)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: "Delete the stats client whose id you provided. <br> If no client with this id exists it will just return 204(no content)." })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let client = await this.clientRepository.findOne({ id: id });
if (!client) { return null; }
await this.clientRepository.delete(client);
return new ResponseStatsClient(client);
}
}

View File

@@ -0,0 +1,124 @@
import { Get, JsonController, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
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 { 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 { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
@JsonController('/stats')
export class StatsController {
@Get()
@ResponseSchema(ResponseStats)
@OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" })
async get() {
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 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'] });
return new ResponseStats(runners, teams, orgs, users, scans, donations)
}
@Get("/runners/distance")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByDistance() {
let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] });
let topRunners = runners.sort((runner1, runner2) => runner1.distance - runner2.distance).slice(0, 9);
let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>();
topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner));
});
return responseRunners;
}
@Get("/runners/donations")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByDonations() {
let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] });
let topRunners = runners.sort((runner1, runner2) => runner1.distanceDonationAmount - runner2.distanceDonationAmount).slice(0, 9);
let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>();
topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner));
});
return responseRunners;
}
@Get("/scans")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten fastest track times (with their runner and the runner's group).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByTrackTime() {
throw new Error("Not implemented yet.")
}
@Get("/teams/distance")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopTeamsByDistance() {
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] });
let topTeams = teams.sort((team1, team2) => team1.distance - team2.distance).slice(0, 9);
let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>();
topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team));
});
return responseTeams;
}
@Get("/teams/donations")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopTeamsByDonations() {
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] });
let topTeams = teams.sort((team1, team2) => team1.distanceDonationAmount - team2.distanceDonationAmount).slice(0, 9);
let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>();
topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team));
});
return responseTeams;
}
@Get("/organisations/distance")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organisations 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 topOrgs = orgs.sort((org1, org2) => org1.distance - org2.distance).slice(0, 9);
let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org));
});
return responseOrgs;
}
@Get("/organisations/donations")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organisations 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 topOrgs = orgs.sort((org1, org2) => org1.distanceDonationAmount - org2.distanceDonationAmount).slice(0, 9);
let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org));
});
return responseOrgs;
}
}

View File

@@ -0,0 +1,31 @@
import { Get, JsonController } from 'routing-controllers';
import { OpenAPI } from 'routing-controllers-openapi';
import { getConnection } from 'typeorm';
import { config } from '../config';
@JsonController()
export class StatusController {
@Get('/status')
@OpenAPI({ description: "A very basic status/health endpoint that just checks if the database connection is available. <br> The available information depth will be expanded later." })
get() {
let connection;
try {
connection = getConnection();
} catch {
throw new Error("sth is wrong, i can feel it....");
}
return {
"controllers": "✔",
"database connection": "✔"
};
}
@Get('/version')
@OpenAPI({ description: "A very basic endpoint that just returns the curent package version." })
getVersion() {
return {
"version": config.version
}
}
}

View File

@@ -1,15 +1,16 @@
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors";
import { CreateTrack } from '../models/actions/CreateTrack';
import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors";
import { CreateTrack } from '../models/actions/create/CreateTrack';
import { UpdateTrack } from '../models/actions/update/UpdateTrack';
import { Track } from '../models/entities/Track';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseTrack } from '../models/responses/ResponseTrack';
import { ScanStationController } from './ScanStationController';
@JsonController('/tracks')
//@Authorized("TRACKS:read")
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class TrackController {
private trackRepository: Repository<Track>;
@@ -21,8 +22,9 @@ export class TrackController {
}
@Get()
@Authorized("TRACK:GET")
@ResponseSchema(ResponseTrack, { isArray: true })
@OpenAPI({ description: "Lists all tracks." })
@OpenAPI({ description: 'Lists all tracks.' })
async getAll() {
let responseTracks: ResponseTrack[] = new Array<ResponseTrack>();
const tracks = await this.trackRepository.find();
@@ -33,10 +35,11 @@ export class TrackController {
}
@Get('/:id')
@Authorized("TRACK:GET")
@ResponseSchema(ResponseTrack)
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
@OnUndefined(TrackNotFoundError)
@OpenAPI({ description: "Returns a track of a specified id (if it exists)" })
@OpenAPI({ description: "Lists all information about the track whose id got provided." })
async getOne(@Param('id') id: number) {
let track = await this.trackRepository.findOne({ id: id });
if (!track) { throw new TrackNotFoundError(); }
@@ -44,44 +47,58 @@ export class TrackController {
}
@Post()
@Authorized("TRACK:CREATE")
@ResponseSchema(ResponseTrack)
@OpenAPI({ description: "Create a new track object (id will be generated automagicly)." })
@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
@OpenAPI({ description: "Create a new track. <br> Please remember that the track\'s distance must be greater than 0." })
async post(
@Body({ validate: true })
track: CreateTrack
) {
return new ResponseTrack(await this.trackRepository.save(track.toTrack()));
return new ResponseTrack(await this.trackRepository.save(await track.toEntity()));
}
@Put('/:id')
@Authorized("TRACK:UPDATE")
@ResponseSchema(ResponseTrack)
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a track object (id can't be changed)." })
async put(@Param('id') id: number, @EntityFromBody() track: Track) {
@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
@OpenAPI({ description: "Update the track whose id you provided. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) updateTrack: UpdateTrack) {
let oldTrack = await this.trackRepository.findOne({ id: id });
if (!oldTrack) {
throw new TrackNotFoundError();
}
if (oldTrack.id != track.id) {
if (oldTrack.id != updateTrack.id) {
throw new TrackIdsNotMatchingError();
}
await this.trackRepository.save(await updateTrack.update(oldTrack));
await this.trackRepository.update(oldTrack, track);
return new ResponseTrack(track);
return new ResponseTrack(await this.trackRepository.findOne({ id: id }));
}
@Delete('/:id')
@Authorized("TRACK:DELETE")
@ResponseSchema(ResponseTrack)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: "Delete a specified track (if it exists)." })
async remove(@Param("id") id: number) {
@OpenAPI({ description: "Delete the track whose id you provided. <br> If no track with this id exists it will just return 204(no content)." })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let track = await this.trackRepository.findOne({ id: id });
if (!track) { return null; }
const trackStations = (await this.trackRepository.findOne({ id: id }, { relations: ["stations"] })).stations;
if (trackStations.length != 0 && !force) {
throw new TrackHasScanStationsError();
}
const stationController = new ScanStationController;
for (let station of trackStations) {
await stationController.remove(station.id, force);
}
await this.trackRepository.delete(track);
return new ResponseTrack(track);
}

View File

@@ -1,15 +1,19 @@
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUser } from '../models/actions/CreateUser';
import { CreateUser } from '../models/actions/create/CreateUser';
import { UpdateUser } from '../models/actions/update/UpdateUser';
import { User } from '../models/entities/User';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseUser } from '../models/responses/ResponseUser';
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
import { PermissionController } from './PermissionController';
@JsonController('/users')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class UserController {
private userRepository: Repository<User>;
@@ -21,68 +25,101 @@ export class UserController {
}
@Get()
@ResponseSchema(User, { isArray: true })
@OpenAPI({ description: 'Lists all users.' })
getAll() {
return this.userRepository.find();
@Authorized("USER:GET")
@ResponseSchema(ResponseUser, { isArray: true })
@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions granted to them.' })
async getAll() {
let responseUsers: ResponseUser[] = new Array<ResponseUser>();
const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] });
users.forEach(user => {
responseUsers.push(new ResponseUser(user));
});
return responseUsers;
}
@Get('/:id')
@ResponseSchema(User)
@Authorized("USER:GET")
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Returns a user of a specified id (if it exists)' })
getOne(@Param('id') id: number) {
return this.userRepository.findOne({ id: id });
@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that all permissions granted to the user will show up here.' })
async getOne(@Param('id') id: number) {
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUser(user);
}
@Get('/:id/permissions')
@Authorized("USER:GET")
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all permissions granted to the user sorted into directly granted and inherited as permission response objects.' })
async getPermissions(@Param('id') id: number) {
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUserPermissions(user);
}
@Post()
@ResponseSchema(User)
@ResponseSchema(UserGroupNotFoundError)
@OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' })
@Authorized("USER:CREATE")
@ResponseSchema(ResponseUser)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: 'Create a new user. <br> If you want to grant permissions to the user you have to create them seperately by posting to /api/permissions after creating the user.' })
async post(@Body({ validate: true }) createUser: CreateUser) {
let user;
try {
user = await createUser.toUser();
user = await createUser.toEntity();
} catch (error) {
throw error;
}
return this.userRepository.save(user);
user = await this.userRepository.save(user)
return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Put('/:id')
@ResponseSchema(User)
@Authorized("USER:UPDATE")
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a user object (id can't be changed)." })
async put(@Param('id') id: number, @EntityFromBody() user: User) {
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: "Update the user whose id you provided. <br> To change the permissions directly granted to the user please use /api/permissions instead. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: id });
if (!oldUser) {
throw new UserNotFoundError();
}
if (oldUser.id != user.id) {
if (oldUser.id != updateUser.id) {
throw new UserIdsNotMatchingError();
}
await this.userRepository.save(await updateUser.update(oldUser));
await this.userRepository.update(oldUser, user);
return user;
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Delete('/:id')
@ResponseSchema(User)
@Authorized("USER:DELETE")
@ResponseSchema(ResponseUser)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a specified runner (if it exists).' })
async remove(@Param("id") id: number) {
@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;
if (!user) { return null; }
const responseUser = await this.userRepository.findOne({ id: 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(user);
return user;
return new ResponseUser(responseUser);
}
}

View File

@@ -1,14 +1,18 @@
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
import { CreateUserGroup } from '../models/actions/create/CreateUserGroup';
import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup';
import { UserGroup } from '../models/entities/UserGroup';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseUserGroup } from '../models/responses/ResponseUserGroup';
import { PermissionController } from './PermissionController';
@JsonController('/usergroups')
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class UserGroupController {
private userGroupsRepository: Repository<UserGroup>;
@@ -20,29 +24,32 @@ export class UserGroupController {
}
@Get()
@Authorized("USERGROUP:GET")
@ResponseSchema(UserGroup, { isArray: true })
@OpenAPI({ description: 'Lists all usergroups.' })
@OpenAPI({ description: 'Lists all groups. <br> The information provided might change while the project continues to evolve.' })
getAll() {
return this.userGroupsRepository.find();
return this.userGroupsRepository.find({ relations: ["permissions"] });
}
@Get('/:id')
@Authorized("USERGROUP:GET")
@ResponseSchema(UserGroup)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@OnUndefined(UserGroupNotFoundError)
@OpenAPI({ description: 'Returns a usergroup of a specified id (if it exists)' })
@OpenAPI({ description: 'Lists all information about the group whose id got provided. <br> The information provided might change while the project continues to evolve.' })
getOne(@Param('id') id: number) {
return this.userGroupsRepository.findOne({ id: id });
return this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
}
@Post()
@Authorized("USERGROUP:CREATE")
@ResponseSchema(UserGroup)
@ResponseSchema(UserGroupNotFoundError)
@OpenAPI({ description: 'Create a new usergroup object (id will be generated automagicly).' })
@OpenAPI({ description: 'Create a new group. <br> If you want to grant permissions to the group you have to create them seperately by posting to /api/permissions after creating the group.' })
async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
let userGroup;
try {
userGroup = await createUserGroup.toUserGroup();
userGroup = await createUserGroup.toEntity();
} catch (error) {
throw error;
}
@@ -51,37 +58,43 @@ export class UserGroupController {
}
@Put('/:id')
@Authorized("USERGROUP:UPDATE")
@ResponseSchema(UserGroup)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a usergroup object (id can't be changed)." })
async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) {
let oldUserGroup = await this.userGroupsRepository.findOne({ id: id });
@OpenAPI({ description: "Update the group whose id you provided. <br> To change the permissions granted to the group please use /api/permissions instead. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @EntityFromBody() updateGroup: UpdateUserGroup) {
let oldGroup = await this.userGroupsRepository.findOne({ id: id });
if (!oldUserGroup) {
throw new UserGroupNotFoundError()
if (!oldGroup) {
throw new UserGroupNotFoundError();
}
if (oldUserGroup.id != userGroup.id) {
if (oldGroup.id != updateGroup.id) {
throw new UserGroupIdsNotMatchingError();
}
await this.userGroupsRepository.save(await updateGroup.update(oldGroup));
await this.userGroupsRepository.update(oldUserGroup, userGroup);
return userGroup;
return (await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })).toResponse();
}
@Delete('/:id')
@ResponseSchema(UserGroup)
@Authorized("USERGROUP:DELETE")
@ResponseSchema(ResponseUserGroup)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a specified usergroup (if it exists).' })
async remove(@Param("id") id: number) {
let group = await this.userGroupsRepository.findOne({ id: id });
if (!group) {
return null;
@OpenAPI({ description: 'Delete the group whose id you provided. <br> If there are any permissions directly granted to the group they will get deleted as well. <br> Users associated with this group won\'t get deleted - just deassociated. <br> If no group with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
let group = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
if (!group) { return null; }
const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });
const permissionController = new PermissionController();
for (let permission of responseGroup.permissions) {
await permissionController.remove(permission.id, true);
}
await this.userGroupsRepository.delete(group);
return group;
return new ResponseUserGroup(responseGroup);
}
}

View File

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

View File

@@ -1,63 +1,57 @@
import { IsString } from 'class-validator';
import { ForbiddenError, NotAcceptableError, NotFoundError, UnauthorizedError } from 'routing-controllers';
/**
* Error to throw when a jwt is expired.
*/
export class ExpiredJWTError extends UnauthorizedError {
@IsString()
name = "ExpiredJWTError"
@IsString()
message = "your provided jwt is expired"
}
/**
* Error to throw when a jwt could not be parsed.
* For example: Wrong signature or expired.
*/
export class IllegalJWTError extends UnauthorizedError {
@IsString()
name = "IllegalJWTError"
@IsString()
message = "your provided jwt could not be parsed"
message = "Your provided jwt could not be parsed."
}
/**
* Error to throw when user is nonexistant or refreshtoken is invalid.
* This can happen if someone provides a JWT with a invalid user id or the refreshTokenCount of the user is higher that the provided jwt's is.
*/
export class UserNonexistantOrRefreshtokenInvalidError extends UnauthorizedError {
@IsString()
name = "UserNonexistantOrRefreshtokenInvalidError"
@IsString()
message = "user is nonexistant or refreshtoken is invalid"
message = "User is nonexistant or refreshtoken is invalid."
}
/**
* Error to throw when provided credentials are invalid.
* We don't have seperate errors for username/mail and passwords to protect against guessing attacks.
*/
export class InvalidCredentialsError extends UnauthorizedError {
@IsString()
name = "InvalidCredentialsError"
@IsString()
message = "your provided credentials are invalid"
message = "Your provided credentials are invalid."
}
/**
* Error to throw when a jwt does not have permission for this route/action.
* Mainly used be the @Authorized decorator (via the authchecker).
*/
export class NoPermissionError extends ForbiddenError {
@IsString()
name = "NoPermissionError"
@IsString()
message = "your provided jwt does not have permission for this route/ action"
message = "Your provided jwt does not have permission for this route/ action."
}
/**
* Error to throw when no username and no email is set.
* Because we have to identify users somehow.
*/
export class UsernameOrEmailNeededError extends NotAcceptableError {
@IsString()
@@ -68,47 +62,48 @@ export class UsernameOrEmailNeededError extends NotAcceptableError {
}
/**
* Error to throw when no password is provided.
* Error to throw when no password is provided for a new user.
* Passwords are the minimum we need for user security.
*/
export class PasswordNeededError extends NotAcceptableError {
@IsString()
name = "PasswordNeededError"
@IsString()
message = "no password is provided - you need to provide it"
message = "No password is provided - you need to provide it."
}
/**
* Error to throw when no user could be found mating the provided credential.
* Error to throw when no user could be found for a certain query.
*/
export class UserNotFoundError extends NotFoundError {
@IsString()
name = "UserNotFoundError"
@IsString()
message = "no user could be found for provided credential"
message = "The user you provided couldn't be located in the system. \n Please check your request."
}
/**
* Error to throw when no jwt token was provided (but one had to be).
* Error to throw when no jwt was provided (but one had to be).
*/
export class JwtNotProvidedError extends NotAcceptableError {
@IsString()
name = "JwtNotProvidedError"
@IsString()
message = "no jwt token was provided"
message = "No jwt was provided."
}
/**
* Error to throw when user was not found or refresh token count was invalid.
* Error to throw when user was not found or the jwt's refresh token count was invalid.
*/
export class UserNotFoundOrRefreshTokenCountInvalidError extends NotAcceptableError {
@IsString()
name = "UserNotFoundOrRefreshTokenCountInvalidError"
@IsString()
message = "user was not found or refresh token count was invalid"
message = "User was not found or the refresh token count is invalid."
}
/**
@@ -119,5 +114,27 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError {
name = "RefreshTokenCountInvalidError"
@IsString()
message = "refresh token count was invalid"
message = "Refresh token count is invalid."
}
/**
* Error to throw when someone tries to reset a user's password more than once in 15 minutes.
*/
export class ResetAlreadyRequestedError extends NotAcceptableError {
@IsString()
name = "ResetAlreadyRequestedError"
@IsString()
message = "You already requested a password reset in the last 15 minutes. \n Please wait until the old reset code expires before requesting a new one."
}
/**
* Error to throw when someone tries a disabled user's password or login as a disabled user.
*/
export class UserDisabledError extends NotAcceptableError {
@IsString()
name = "UserDisabledError"
@IsString()
message = "This user is currently disabled. \n Please contact your administrator if this is a mistake."
}

View File

@@ -0,0 +1,25 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a Donation couldn't be found.
*/
export class DonationNotFoundError extends NotFoundError {
@IsString()
name = "DonationNotFoundError"
@IsString()
message = "Donation not found!"
}
/**
* Error to throw when two Donations' ids don't match.
* Usually occurs when a user tries to change a Donation's id.
*/
export class DonationIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "DonationIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a Donation's id: This isn't allowed!"
}

47
src/errors/DonorErrors.ts Normal file
View File

@@ -0,0 +1,47 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a donor couldn't be found.
*/
export class DonorNotFoundError extends NotFoundError {
@IsString()
name = "DonorNotFoundError"
@IsString()
message = "Donor not found!"
}
/**
* Error to throw when two donors' ids don't match.
* Usually occurs when a user tries to change a donor's id.
*/
export class DonorIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "DonorIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a donor's id: This isn't allowed!"
}
/**
* Error to throw when a donor needs a receipt, but no address is associated with them.
*/
export class DonorReceiptAddressNeededError extends NotAcceptableError {
@IsString()
name = "DonorReceiptAddressNeededError"
@IsString()
message = "An address is needed to create a receipt for a donor. \n You didn't provide one."
}
/**
* Error to throw when a donor still has donations associated.
*/
export class DonorHasDonationsError extends NotAcceptableError {
@IsString()
name = "DonorHasDonationsError"
@IsString()
message = "This donor still has donations associated with it. \n If you want to delete this donor with all it's donations and teams add `?force` to your query."
}

View File

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

View File

@@ -0,0 +1,36 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a permission couldn't be found.
*/
export class PermissionNotFoundError extends NotFoundError {
@IsString()
name = "PermissionNotFoundError"
@IsString()
message = "Permission not found!"
}
/**
* Error to throw when two permissions' ids don't match.
* Usually occurs when a user tries to change a permission's id.
*/
export class PermissionIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "PermissionIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a permission's id: This isn't allowed!"
}
/**
* Error to throw when a permission gets provided without a principal.
*/
export class PermissionNeedsPrincipalError extends NotAcceptableError {
@IsString()
name = "PermissionNeedsPrincipalError"
@IsString()
message = "You provided no principal for this permission."
}

View File

@@ -0,0 +1,24 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a user couldn't be found.
*/
export class PrincipalNotFoundError extends NotFoundError {
@IsString()
name = "PrincipalNotFoundError"
@IsString()
message = "Principal not found!"
}
/**
* Error to throw, when a provided runner organization doesn't belong to the accepted types.
*/
export class PrincipalWrongTypeError extends NotAcceptableError {
@IsString()
name = "PrincipalWrongTypeError"
@IsString()
message = "The principal must have an existing principal's id. \n You provided a object of another type."
}

View File

@@ -0,0 +1,48 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a card couldn't be found.
*/
export class RunnerCardNotFoundError extends NotFoundError {
@IsString()
name = "RunnerCardNotFoundError"
@IsString()
message = "Card not found!"
}
/**
* Error to throw when two cards' ids don't match.
* Usually occurs when a user tries to change a card's id.
*/
export class RunnerCardIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerCardIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a cards's id: This isn't allowed"
}
/**
* Error to throw when a card still has scans associated.
*/
export class RunnerCardHasScansError extends NotAcceptableError {
@IsString()
name = "RunnerCardHasScansError"
@IsString()
message = "This card still has scans associated with it. \n If you want to delete this card with all it's scans add `?force` to your query. \n Otherwise please consider just disabling it."
}
/**
* Error to throw when a card's id is too big to generate a ean-13 barcode for it.
* This error should never reach a end user.
*/
export class RunnerCardIdOutOfRangeError extends Error {
@IsString()
name = "RunnerCardIdOutOfRangeError"
@IsString()
message = "The card's id is too big to fit into a ean-13 barcode. \n This has a very low probability of happening but means that you might want to switch your barcode format for something that can accept numbers over 9999999999."
}

View File

@@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerNotFoundError extends NotFoundError {
@IsString()
@@ -16,14 +15,13 @@ export class RunnerNotFoundError extends NotFoundError {
/**
* Error to throw when two runners' ids don't match.
* Usually occurs when a user tries to change a runner's id.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!"
}
/**
@@ -34,5 +32,16 @@ export class RunnerGroupNeededError extends NotAcceptableError {
name = "RunnerGroupNeededError"
@IsString()
message = "Runner's need to be part of one group (team or organisiation)! \n You provided neither."
message = "Runner's need to be part of one group (team or organisation)! \n You provided neither."
}
/**
* Error to throw when a runner still has distance donations associated.
*/
export class RunnerHasDistanceDonationsError extends NotAcceptableError {
@IsString()
name = "RunnerHasDistanceDonationsError"
@IsString()
message = "This runner still has distance donations associated with it. \n If you want to delete this runner with all it's donations and teams add `?force` to your query."
}

View File

@@ -3,7 +3,6 @@ import { NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner group couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerGroupNotFoundError extends NotFoundError {
@IsString()

View File

@@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner organisation couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerOrganisationNotFoundError extends NotFoundError {
@IsString()
@@ -14,40 +13,37 @@ export class RunnerOrganisationNotFoundError extends NotFoundError {
}
/**
* Error to throw when two runner organisations' ids don't match.
* Usually occurs when a user tries to change a runner's id.
* Implemented this way to work with the json-schema conversion for openapi.
* 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 id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
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.
* Implemented this waysto work with the json-schema conversion for openapi.
*/
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 ass `?force` to your query."
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 runners associated.
* Implemented this waysto work with the json-schema conversion for openapi.
* 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 ass `?force` to your query."
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."
}
/**

View File

@@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a runner team couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class RunnerTeamNotFoundError extends NotFoundError {
@IsString()
@@ -15,32 +14,29 @@ export class RunnerTeamNotFoundError extends NotFoundError {
/**
* Error to throw when two runner teams' ids don't match.
* Usually occurs when a user tries to change a runner's id.
* Implemented this way to work with the json-schema conversion for openapi.
* Usually occurs when a user tries to change a runner team's id.
*/
export class RunnerTeamIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "RunnerTeamIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!"
}
/**
* Error to throw when a team still has runners associated.
* Implemented this waysto work with the json-schema conversion for openapi.
*/
export class RunnerTeamHasRunnersError extends NotAcceptableError {
@IsString()
name = "RunnerTeamHasRunnersError"
@IsString()
message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams ass `?force` to your query."
message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams add `?force` to your query."
}
/**
* Error to throw when a team still has runners associated.
* Implemented this waysto work with the json-schema conversion for openapi.
*/
export class RunnerTeamNeedsParentError extends NotAcceptableError {
@IsString()

25
src/errors/ScanErrors.ts Normal file
View File

@@ -0,0 +1,25 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a Scan couldn't be found.
*/
export class ScanNotFoundError extends NotFoundError {
@IsString()
name = "ScanNotFoundError"
@IsString()
message = "Scan not found!"
}
/**
* Error to throw when two Scans' ids don't match.
* Usually occurs when a user tries to change a Scan's id.
*/
export class ScanIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "ScanIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a Scan's id: This isn't allowed!"
}

View File

@@ -0,0 +1,36 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw, when a non-existent scan station get's loaded.
*/
export class ScanStationNotFoundError extends NotFoundError {
@IsString()
name = "ScanStationNotFoundError"
@IsString()
message = "The scan station you provided couldn't be located in the system. \n Please check your request."
}
/**
* Error to throw when two scan stations' ids don't match.
* Usually occurs when a user tries to change a scan station's id.
*/
export class ScanStationIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "ScanStationIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a scan station's id: This isn't allowed!"
}
/**
* Error to throw when a station still has scans associated.
*/
export class ScanStationHasScansError extends NotAcceptableError {
@IsString()
name = "ScanStationHasScansError"
@IsString()
message = "This station still has scans associated with it. \n If you want to delete this station with all it's scans add `?force` to your query."
}

View File

@@ -0,0 +1,25 @@
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw, when a non-existent stats client get's loaded.
*/
export class StatsClientNotFoundError extends NotFoundError {
@IsString()
name = "StatsClientNotFoundError"
@IsString()
message = "The stats client you provided couldn't be located in the system. \n Please check your request."
}
/**
* Error to throw when two stats clients' ids don't match.
* Usually occurs when a user tries to change a stats client's id.
*/
export class StatsClientIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "StatsClientIdsNotMatchingError"
@IsString()
message = "The ids don't match! \n And if you wanted to change a stats client's id: This isn't allowed!"
}

View File

@@ -1,9 +1,8 @@
import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError } from 'routing-controllers';
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when a track couldn't be found.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class TrackNotFoundError extends NotFoundError {
@IsString()
@@ -16,12 +15,30 @@ export class TrackNotFoundError extends NotFoundError {
/**
* Error to throw when two tracks' ids don't match.
* Usually occurs when a user tries to change a track's id.
* Implemented this ways to work with the json-schema conversion for openapi.
*/
export class TrackIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "TrackIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a track's id: This isn't allowed"
message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed"
}
/**
* Error to throw when a track's lap time is set to a negative value.
*/
export class TrackLapTimeCantBeNegativeError extends NotAcceptableError {
@IsString()
name = "TrackLapTimeCantBeNegativeError"
@IsString()
message = "The minimum lap time you provided is negative - That isn't possible. \n If you wanted to disable it: Just set it to 0/null."
}
export class TrackHasScanStationsError extends NotAcceptableError {
@IsString()
name = "TrackHasScanStationsError"
@IsString()
message = "This track still has stations associated with it. \n If you want to delete this track with all it's stations and scans add `?force` to your query."
}

View File

@@ -3,14 +3,39 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when no username or email is set
* Error to throw when no username or email is set.
* We somehow need to identify you on login.
*/
export class UsernameOrEmailNeededError extends NotFoundError {
@IsString()
name = "UsernameOrEmailNeededError"
@IsString()
message = "no username or email is set!"
message = "No username or email is set!"
}
/**
* Error to throw when no username contains illegal characters.
* Right now the only one is "@" but this could change in the future.
*/
export class UsernameContainsIllegalCharacterError extends NotAcceptableError {
@IsString()
name = "UsernameContainsIllegalCharacterError"
@IsString()
message = "The provided username contains illegal characters! \n Right now the following characters are considered illegal: '@'"
}
/**
* Error to throw when no email is set.
* We somehow need to identify you :)
*/
export class UserEmailNeededError extends NotFoundError {
@IsString()
name = "UserEmailNeededError"
@IsString()
message = "No email is set! \n You have to provide email addresses for users (used for password reset among others)."
}
/**
@@ -33,5 +58,17 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
name = "UserIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n And if you wanted to change a user's id: This isn't allowed"
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

@@ -2,18 +2,18 @@ import { IsString } from 'class-validator';
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when no groupname is set
* Error to throw when no group name is set.
*/
export class GroupNameNeededError extends NotFoundError {
@IsString()
name = "GroupNameNeededError"
@IsString()
message = "no groupname is set!"
message = "No name is set for this group!"
}
/**
* Error to throw when a usergroup couldn't be found.
* Error to throw when a user group couldn't be found.
*/
export class UserGroupNotFoundError extends NotFoundError {
@IsString()
@@ -24,13 +24,13 @@ export class UserGroupNotFoundError extends NotFoundError {
}
/**
* Error to throw when two usergroups' ids don't match.
* Usually occurs when a user tries to change a usergroups's id.
* Error to throw when two user groups' ids don't match.
* Usually occurs when a user tries to change a user groups's id.
*/
export class UserGroupIdsNotMatchingError extends NotAcceptableError {
@IsString()
name = "UserGroupIdsNotMatchingError"
@IsString()
message = "The id's don't match!! \n If you wanted to change a usergroup's id: This isn't allowed"
message = "The ids don't match!! \n If you wanted to change a user group's id: This isn't allowed!"
}

111
src/jwtcreator.ts Normal file
View File

@@ -0,0 +1,111 @@
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
import * as jsonwebtoken from "jsonwebtoken";
import { config } from './config';
import { User } from './models/entities/User';
/**
* This class is responsible for all things JWT creation.
*/
export class JwtCreator {
/**
* Creates a new refresh token for a given user
* @param user User entity that the refresh token shall be created for
* @param expiry_timestamp Timestamp for the token expiry. Will be generated if not provided.
*/
public static createRefresh(user: User, expiry_timestamp?: number) {
if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 10 * 36000; }
return jsonwebtoken.sign({
refreshTokenCount: user.refreshTokenCount,
id: user.id,
exp: expiry_timestamp
}, config.jwt_secret)
}
/**
* Creates a new access token for a given user
* @param user User entity that the access token shall be created for
* @param expiry_timestamp Timestamp for the token expiry. Will be generated if not provided.
*/
public static createAccess(user: User, expiry_timestamp?: number) {
if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 10 * 36000; }
return jsonwebtoken.sign({
userdetails: new JwtUser(user),
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.
* @param user User entity that the password reset token shall be created for
*/
public static createReset(user: User) {
let expiry_timestamp = Math.floor(Date.now() / 1000) + 15 * 60;
return jsonwebtoken.sign({
id: user.id,
refreshTokenCount: user.refreshTokenCount,
exp: expiry_timestamp
}, config.jwt_secret)
}
}
/**
* Special variant of the user class that
*/
export class JwtUser {
@IsInt()
id: number;
@IsUUID(4)
uuid: string;
@IsOptional()
@IsEmail()
email?: string;
@IsOptional()
@IsString()
username?: string;
@IsString()
@IsNotEmpty()
firstname: string;
@IsString()
@IsOptional()
middlename?: string;
@IsString()
@IsNotEmpty()
lastname: string;
permissions: string[];
@IsBoolean()
enabled: boolean;
@IsInt()
@IsNotEmpty()
refreshTokenCount?: number;
@IsString()
@IsOptional()
profilePic?: string;
/**
* Creates a new instance of this class based on a provided user entity.
* @param user User entity that shall be encapsulated in a jwt.
*/
public constructor(user: User) {
this.id = user.id;
this.firstname = user.firstname;
this.middlename = user.middlename;
this.lastname = user.lastname;
this.username = user.username;
this.email = user.email;
this.refreshTokenCount = user.refreshTokenCount;
this.uuid = user.uuid;
this.profilePic = user.profilePic;
this.permissions = user.allPermissions;
}
}

View File

@@ -4,6 +4,7 @@ import { User } from '../models/entities/User';
import SeedUsers from '../seeds/SeedUsers';
/**
* Loader for the database that creates the database connection and initializes the database tabels.
* It also triggers the seeding process if no users got detected in the database.
*/
export default async () => {
const connection = await createConnection();

View File

@@ -2,10 +2,12 @@ import cookieParser from "cookie-parser";
import { Application } from "express";
/**
* Loader for express related configurations.
* Currently only enables the proxy trust.
* Configures proxy trusts, globally used middlewares and other express features.
*/
export default async (app: Application) => {
app.enable('trust proxy');
app.disable('x-powered-by');
app.disable('x-served-by');
app.use(cookieParser());
return app;
};

View File

@@ -5,6 +5,7 @@ import openapiLoader from "./openapi";
/**
* Index Loader that executes the other loaders in the right order.
* This basicly exists for abstraction and a overall better dev experience.
*/
export default async (app: Application) => {
await databaseLoader();

View File

@@ -1,11 +1,12 @@
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
import { Application } from "express";
import { validationMetadatasToSchemas } from "@odit/class-validator-jsonschema";
import express, { Application } from "express";
import path from 'path';
import { getMetadataArgsStorage } from "routing-controllers";
import { routingControllersToSpec } from "routing-controllers-openapi";
import * as swaggerUiExpress from "swagger-ui-express";
import { generateSpec } from '../apispec';
/**
* Loader for everything openapi related - from creating the schema to serving it via a static route.
* Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
* All auth schema related stuff also has to be configured here
*/
export default async (app: Application) => {
const storage = getMetadataArgsStorage();
@@ -14,41 +15,10 @@ export default async (app: Application) => {
});
//Spec creation based on the previously created schemas
const spec = routingControllersToSpec(
storage,
{
routePrefix: "/api"
},
{
components: {
schemas,
"securitySchemes": {
"AuthToken": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
},
info: {
description: "The the backend API for the LfK! runner system.",
title: "LfK! Backend API",
version: "1.0.0",
},
}
);
//Options for swaggerUiExpress
const options = {
explorer: true,
};
app.use(
"/api/docs",
swaggerUiExpress.serve,
swaggerUiExpress.setup(spec, options)
);
app.get(["/api/openapi.json", "/api/swagger.json"], (req, res) => {
const spec = generateSpec(storage, schemas);
app.get(["/api/docs/openapi.json", "/api/docs/swagger.json"], (req, res) => {
res.json(spec);
});
app.use('/api/docs', express.static(path.join(__dirname, '../static/docs'), { index: "index.html", extensions: ['html'] }));
return app;
};

View File

@@ -1,7 +1,7 @@
import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers";
/**
* Our Error handling middlware that returns our custom httperrors to the user
* Our Error handling middlware that returns our custom httperrors to the user.
*/
@Middleware({ type: "after" })
export class ErrorHandler implements ExpressErrorMiddlewareInterface {

View File

@@ -1,5 +1,10 @@
import { Request, Response } from 'express';
/**
* Custom express middleware that appends the raw body to the request object.
* Mainly used for parsing csvs from bodies.
*/
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
const body = []
req.on('data', chunk => {
@@ -8,15 +13,6 @@ const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
req.on('end', () => {
const rawBody = Buffer.concat(body)
req['rawBody'] = rawBody
/*
switch (req.header('content-type')) {
case 'application/json':
req.body = JSON.parse(rawBody.toString())
break
// add more body parsing if needs be
default:
}
*/
next()
})
req.on('error', () => {

View File

@@ -0,0 +1,69 @@
import * as argon2 from "argon2";
import { Request, Response } from 'express';
import { getConnectionManager } from 'typeorm';
import { ScanStation } from '../models/entities/ScanStation';
import authchecker from './authchecker';
/**
* This middleware handles the authentication of scan station api tokens.
* The tokens have to be provided via Bearer authorization header.
* You have to manually use this middleware via @UseBefore(ScanAuth) instead of using @Authorized().
* @param req Express request object.
* @param res Express response object.
* @param next Next function to call on success.
*/
const ScanAuth = async (req: Request, res: Response, next: () => void) => {
let provided_token: string = req.headers["authorization"];
if (provided_token == "" || provided_token === undefined || provided_token === null) {
res.status(401).send("No api token provided.");
return;
}
try {
provided_token = provided_token.replace("Bearer ", "");
} catch (error) {
res.status(401).send("No valid jwt or api token provided.");
return;
}
let prefix = "";
try {
prefix = provided_token.split(".")[0];
}
finally {
if (prefix == "" || prefix == undefined || prefix == null) {
res.status(401).send("Api token non-existent or invalid syntax.");
return;
}
}
const station = await getConnectionManager().get().getRepository(ScanStation).findOne({ prefix: prefix });
if (!station) {
let user_authorized = false;
try {
let action = { request: req, response: res, context: null, next: next }
user_authorized = await authchecker(action, ["SCAN:CREATE"]);
}
finally {
if (user_authorized == false) {
res.status(401).send("Api token non-existent or invalid syntax.");
return;
}
else {
next();
}
}
}
else {
if (station.enabled == false) {
res.status(401).send("Station disabled.");
}
if (!(await argon2.verify(station.key, provided_token))) {
res.status(401).send("Api token invalid.");
return;
}
next();
}
}
export default ScanAuth;

View File

@@ -0,0 +1,66 @@
import * as argon2 from "argon2";
import { Request, Response } from 'express';
import { getConnectionManager } from 'typeorm';
import { StatsClient } from '../models/entities/StatsClient';
import authchecker from './authchecker';
/**
* This middleware handles the authentication of stats client api tokens.
* The tokens have to be provided via Bearer authorization header.
* You have to manually use this middleware via @UseBefore(StatsAuth) instead of using @Authorized().
* @param req Express request object.
* @param res Express response object.
* @param next Next function to call on success.
*/
const StatsAuth = async (req: Request, res: Response, next: () => void) => {
let provided_token: string = req.headers["authorization"];
if (provided_token == "" || provided_token === undefined || provided_token === null) {
res.status(401).send("No api token provided.");
return;
}
try {
provided_token = provided_token.replace("Bearer ", "");
} catch (error) {
res.status(401).send("No valid jwt or api token provided.");
return;
}
let prefix = "";
try {
prefix = provided_token.split(".")[0];
}
finally {
if (prefix == "" || prefix == undefined || prefix == null) {
res.status(401).send("Api token non-existant or invalid syntax.");
return;
}
}
const client = await getConnectionManager().get().getRepository(StatsClient).findOne({ prefix: prefix });
if (!client) {
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"]);
}
finally {
if (user_authorized == false) {
res.status(401).send("Api token non-existant or invalid syntax.");
return;
}
else {
next();
}
}
}
else {
if (!(await argon2.verify(client.key, provided_token))) {
res.status(401).send("Api token invalid.");
return;
}
next();
}
}
export default StatsAuth;

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

@@ -0,0 +1,74 @@
import cookie from "cookie";
import * as jwt from "jsonwebtoken";
import { Action } from "routing-controllers";
import { getConnectionManager } from 'typeorm';
import { config } from '../config';
import { IllegalJWTError, NoPermissionError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from '../errors/AuthError';
import { JwtCreator, JwtUser } from '../jwtcreator';
import { User } from '../models/entities/User';
/**
* Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
* @param permissions The permissions that the endpoint using @Authorized requires.
*/
const authchecker = async (action: Action, permissions: string[] | string) => {
let required_permissions = undefined;
if (typeof permissions === "string") {
required_permissions = [permissions]
} else {
required_permissions = permissions
}
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"] }, { relations: ['permissions'] })
if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
if (user.enabled == false) { throw new UserDisabledError(); }
if (!jwtPayload["permissions"]) { throw new NoPermissionError(); }
action.response.local = {}
action.response.local.jwtPayload = jwtPayload;
for (let required_permission of required_permissions) {
if (!(jwtPayload["permissions"].includes(required_permission))) { return false; }
}
return true;
}
/**
* 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 authchecker

View File

@@ -1,64 +0,0 @@
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
import { Address } from '../entities/Address';
export class CreateAddress {
/**
* The address's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* The address's first line.
* Containing the street and house number.
*/
@IsString()
@IsNotEmpty()
address1: string;
/**
* The address's second line.
* Containing optional information.
*/
@IsString()
@IsOptional()
address2?: string;
/**
* The address's postal code.
*/
@IsString()
@IsNotEmpty()
@IsPostalCode("DE")
postalcode: string;
/**
* The address's city.
*/
@IsString()
@IsNotEmpty()
city: string;
/**
* The address's country.
*/
@IsString()
@IsNotEmpty()
country: string;
/**
* Creates a Address object based on this.
*/
public toAddress(): Address {
let newAddress: Address = new Address();
newAddress.address1 = this.address1;
newAddress.address2 = this.address2;
newAddress.postalcode = this.postalcode;
newAddress.city = this.city;
newAddress.country = this.country;
return newAddress;
}
}

View File

@@ -1,59 +0,0 @@
import * as argon2 from "argon2";
import { IsEmail, IsOptional, IsString } from 'class-validator';
import * as jsonwebtoken from 'jsonwebtoken';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth';
export class CreateAuth {
@IsOptional()
@IsString()
username?: string;
@IsString()
password: string;
@IsOptional()
@IsEmail()
@IsString()
email?: string;
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
}
if (!this.password) {
throw new PasswordNeededError()
}
const found_users = await getConnectionManager().get().getRepository(User).find({ relations: ['groups', 'permissions'], where: [{ username: this.username }, { email: this.email }] });
if (found_users.length === 0) {
throw new UserNotFoundError()
} else {
const found_user = found_users[0]
if (await argon2.verify(found_user.password, this.password + found_user.uuid)) {
const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
found_user.permissions = found_user.permissions || []
delete found_user.password;
newAuth.access_token = jsonwebtoken.sign({
userdetails: found_user,
exp: timestamp_accesstoken_expiry
}, config.jwt_secret)
newAuth.access_token_expires_at = timestamp_accesstoken_expiry
//
const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
newAuth.refresh_token = jsonwebtoken.sign({
refreshtokencount: found_user.refreshTokenCount,
userid: found_user.id,
exp: timestamp_refresh_expiry
}, config.jwt_secret)
newAuth.refresh_token_expires_at = timestamp_refresh_expiry
} else {
throw new InvalidCredentialsError()
}
}
return newAuth;
}
}

View File

@@ -1,84 +0,0 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
import { GroupContact } from '../entities/GroupContact';
export class CreateGroupContact {
/**
* The contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The contact's middle name.
* Optional
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The contact's address.
* Optional
*/
@IsInt()
@IsOptional()
address?: number;
/**
* The contact's phone number.
* Optional
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The contact's email address.
* Optional
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* Get's this participant's address from this.address.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
/**
* Creates a Address object based on this.
*/
public async toGroupContact(): Promise<GroupContact> {
let contact: GroupContact = new GroupContact();
contact.firstname = this.firstname;
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.email = this.email;
contact.phone = this.phone;
contact.address = await this.getAddress();
return null;
}
}

View File

@@ -1,72 +0,0 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
export abstract class CreateParticipant {
/**
* The new participant's first name.
*/
@IsString()
@IsNotEmpty()
firstname: string;
/**
* The new participant's middle name.
* Optional.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new participant's last name.
*/
@IsString()
@IsNotEmpty()
lastname: string;
/**
* The new participant's phone number.
* Optional.
*/
@IsString()
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The new participant's e-mail address.
* Optional.
*/
@IsString()
@IsOptional()
@IsEmail()
email?: string;
/**
* The new participant's address.
* Must be of type number (address id).
* Optional.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Get's this participant's address from this.address.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
}

View File

@@ -1,37 +0,0 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors';
import { GroupContact } from '../entities/GroupContact';
export abstract class CreateRunnerGroup {
/**
* The group's name.
*/
@IsNotEmpty()
@IsString()
name: string;
/**
* The group's contact.
* Optional
*/
@IsInt()
@IsOptional()
contact?: number;
/**
* Get's this group's contact from this.address.
*/
public async getContact(): Promise<GroupContact> {
if (this.contact === undefined || this.contact === null) {
return null;
}
if (!isNaN(this.contact)) {
let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
if (!contact) { throw new GroupContactNotFoundError; }
return contact;
}
throw new GroupContactWrongTypeError;
}
}

View File

@@ -1,46 +0,0 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
export class CreateRunnerOrganisation extends CreateRunnerGroup {
/**
* The new organisation's address.
* Must be of type number (address id).
* Optional.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Get's this org's address from this.address.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
/**
* Creates a RunnerOrganisation entity from this.
*/
public async toRunnerOrganisation(): Promise<RunnerOrganisation> {
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
newRunnerOrganisation.name = this.name;
newRunnerOrganisation.contact = await this.getContact();
newRunnerOrganisation.address = await this.getAddress();
return newRunnerOrganisation;
}
}

View File

@@ -1,44 +0,0 @@
import { IsInt, IsNotEmpty } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
export class CreateRunnerTeam extends CreateRunnerGroup {
/**
* The team's parent group (organisation).
*/
@IsInt()
@IsNotEmpty()
parentGroup: number;
public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.parentGroup)) {
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
return parentGroup;
}
throw new RunnerOrganisationWrongTypeError;
}
/**
* Creates a RunnerTeam entity from this.
*/
public async toRunnerTeam(): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam();
newRunnerTeam.name = this.name;
newRunnerTeam.parentGroup = await this.getParent();
newRunnerTeam.contact = await this.getContact()
return newRunnerTeam;
}
}

View File

@@ -1,30 +0,0 @@
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
import { Track } from '../entities/Track';
export class CreateTrack {
/**
* The track's name.
*/
@IsString()
@IsNotEmpty()
name: string;
/**
* The track's distance in meters (must be greater than 0).
*/
@IsInt()
@IsPositive()
distance: number;
/**
* Converts a Track object based on this.
*/
public toTrack(): Track {
let newTrack: Track = new Track();
newTrack.name = this.name;
newTrack.distance = this.distance;
return newTrack;
}
}

View File

@@ -1,120 +0,0 @@
import * as argon2 from "argon2";
import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../config';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup';
export class CreateUser {
/**
* The new user's first name.
*/
@IsString()
firstname: string;
/**
* The new user's middle name.
* Optinal.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new user's last name.
*/
@IsString()
lastname: string;
/**
* The new user's username.
* You have to provide at least one of: {email, username}.
*/
@IsOptional()
@IsString()
username?: string;
/**
* The new user's email address.
* You have to provide at least one of: {email, username}.
*/
@IsEmail()
@IsString()
@IsOptional()
email?: string;
/**
* The new user's phone number.
* Optional
*/
@IsPhoneNumber(config.phone_validation_countrycode)
@IsOptional()
phone?: string;
/**
* The new user's password.
* This will of course not be saved in plaintext :)
*/
@IsString()
password: string;
/**
* The new user's groups' id(s).
* You can provide either one groupId or an array of groupIDs.
* Optional.
*/
@IsOptional()
groupId?: number[] | number
//TODO: ProfilePics
/**
* Converts this to a User Entity.
*/
public async toUser(): Promise<User> {
let newUser: User = new User();
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
}
if (this.groupId) {
if (!Array.isArray(this.groupId)) {
this.groupId = [this.groupId]
}
const groupIDs: number[] = this.groupId
let errors = 0
const validateusergroups = async () => {
let foundgroups = []
for (const g of groupIDs) {
const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g });
if (found.length === 0) {
errors++
} else {
foundgroups.push(found[0])
}
}
newUser.groups = foundgroups
}
await validateusergroups()
if (errors !== 0) {
throw new UserGroupNotFoundError();
}
}
newUser.email = this.email
newUser.username = this.username
newUser.firstname = this.firstname
newUser.middlename = this.middlename
newUser.lastname = this.lastname
newUser.uuid = uuid.v4()
newUser.phone = this.phone
newUser.password = await argon2.hash(this.password + newUser.uuid);
//TODO: ProfilePics
return newUser;
}
}

View File

@@ -6,11 +6,23 @@ import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, Us
import { User } from '../entities/User';
import { Logout } from '../responses/ResponseLogout';
/**
* This class handels a user logging out of the system.
* Of course it check's the user's provided credential (token) before logging him out.
*/
export class HandleLogout {
/**
* A stringyfied jwt access token.
* Will get checked for validity.
*/
@IsString()
@IsOptional()
token?: string;
/**
* Logs the user out.
* This gets achived by increasing the user's refresh token count, thereby invalidateing all currently existing jwts for that user.
*/
public async logout(): Promise<Logout> {
let logout: Logout = new Logout();
if (!this.token || this.token === undefined) {
@@ -23,11 +35,11 @@ export class HandleLogout {
throw new IllegalJWTError()
}
logout.timestamp = Math.floor(Date.now() / 1000)
let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] });
let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] });
if (!found_user) {
throw new UserNotFoundError()
}
if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) {
throw new RefreshTokenCountInvalidError()
}
found_user.refreshTokenCount++;

View File

@@ -5,8 +5,12 @@ import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisation
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunner } from './CreateRunner';
import { CreateRunner } from './create/CreateRunner';
/**
* Special class used to import runners from csv files - or json arrays created from csv to be exact.
* Why you ask? Because the past has shown us that a non excel/csv based workflow is too much for most schools.
*/
export class ImportRunner {
/**
@@ -18,7 +22,6 @@ export class ImportRunner {
/**
* The new runner's middle name.
* Optional.
*/
@IsString()
@IsOptional()
@@ -32,18 +35,26 @@ export class ImportRunner {
lastname: string;
/**
* The new runner's class (if not provided otherwise).
* The new runner's team's name (if not provided otherwise).
* The team will automaticly get generated if it doesn't exist in this org yet.
*/
@IsString()
@IsOptional()
team?: string;
/**
* Just an alias for team, because this is usually only used for importing data from schools.
*/
@IsOptional()
@IsString()
public set class(value: string) {
this.team = value;
}
/**
* Creates a CreateRunner object based on this.
* @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of.
*/
public async toCreateRunner(groupID: number): Promise<CreateRunner> {
let newRunner: CreateRunner = new CreateRunner();
@@ -55,6 +66,10 @@ export class ImportRunner {
return newRunner;
}
/**
* Get's the new runners group.
* @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of.
*/
public async getGroup(groupID: number): Promise<RunnerGroup> {
if (this.team === undefined && groupID === undefined) {
throw new RunnerGroupNeededError();

View File

@@ -2,17 +2,30 @@ import { IsOptional, IsString } from 'class-validator';
import * as jsonwebtoken from 'jsonwebtoken';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
import { JwtCreator } from "../../jwtcreator";
import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth';
import { ResponseAuth } from '../responses/ResponseAuth';
/**
* This class is used to create refreshed auth credentials.
* To be a little bit more exact: Is takes in a refresh token and creates a new access and refresh token for it's user.
* It of course checks for user existance, jwt validity and so on.
*/
export class RefreshAuth {
/**
* A stringyfied jwt refresh token.
* Will get checked for validity.
*/
@IsString()
@IsOptional()
token?: string;
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<ResponseAuth> {
let newAuth: ResponseAuth = new ResponseAuth();
if (!this.token || this.token === undefined) {
throw new JwtNotProvidedError()
}
@@ -22,31 +35,22 @@ export class RefreshAuth {
} catch (error) {
throw new IllegalJWTError()
}
const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] }, { relations: ['groups', 'permissions'] });
const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] }, { relations: ['groups', 'permissions', 'groups.permissions'] });
if (!found_user) {
throw new UserNotFoundError()
}
if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
if (found_user.enabled == false) { throw new UserDisabledError(); }
if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) {
throw new RefreshTokenCountInvalidError()
}
found_user.permissions = found_user.permissions || []
delete found_user.password;
//Create the auth token
const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
delete found_user.password;
newAuth.access_token = jsonwebtoken.sign({
userdetails: found_user,
exp: timestamp_accesstoken_expiry
}, config.jwt_secret)
newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry);
newAuth.access_token_expires_at = timestamp_accesstoken_expiry
//
//Create the refresh token
const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
newAuth.refresh_token = jsonwebtoken.sign({
refreshtokencount: found_user.refreshTokenCount,
userid: found_user.id,
exp: timestamp_refresh_expiry
}, config.jwt_secret)
newAuth.refresh_token_expires_at = timestamp_refresh_expiry
newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry);
newAuth.refresh_token_expires_at = timestamp_refresh_expiry;
return newAuth;
}
}

View File

@@ -0,0 +1,57 @@
import * as argon2 from "argon2";
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import * as jsonwebtoken from 'jsonwebtoken';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { IllegalJWTError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
import { User } from '../entities/User';
/**
* This class can be used to reset a user's password.
* To set a new password the user needs to provide a valid password reset token.
*/
export class ResetPassword {
/**
* The reset token on which the password reset will be based.
*/
@IsOptional()
@IsString()
resetToken?: string;
/**
* The user's new password
*/
@IsNotEmpty()
@IsString()
password: string;
/**
* Create a password reset token based on this.
*/
public async resetPassword(): Promise<any> {
if (!this.resetToken || this.resetToken === undefined) {
throw new JwtNotProvidedError()
}
if (!this.password || this.password === undefined) {
throw new PasswordNeededError()
}
let decoded;
try {
decoded = jsonwebtoken.verify(this.resetToken, config.jwt_secret)
} catch (error) {
throw new IllegalJWTError()
}
const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] });
if (!found_user) { throw new UserNotFoundError(); }
if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { throw new RefreshTokenCountInvalidError(); }
found_user.refreshTokenCount = found_user.refreshTokenCount + 1;
found_user.password = await argon2.hash(this.password + found_user.uuid);
await getConnectionManager().get().getRepository(User).save(found_user);
return "password reset successfull";
}
}

View File

@@ -1,57 +0,0 @@
import { IsInt, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
export class UpdateRunner extends CreateParticipant {
/**
* The updated runner's id.
*/
@IsInt()
id: number;
/**
* The updated runner's new team/org.
*/
@IsObject()
group: RunnerGroup;
/**
* Creates a Runner entity from this.
*/
public async toRunner(): Promise<Runner> {
let newRunner: Runner = new Runner();
newRunner.id = this.id;
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();
newRunner.address = await this.getAddress();
return newRunner;
}
/**
* Manages all the different ways a group can be provided.
*/
public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.group.id)) {
let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group.id });
if (!group) { throw new RunnerGroupNotFoundError; }
return group;
}
throw new RunnerOrganisationWrongTypeError;
}
}

View File

@@ -1,50 +0,0 @@
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
export class UpdateRunnerTeam extends CreateRunnerGroup {
/**
* The updated team's id.
*/
@IsInt()
id: number;
/**
* The team's parent group (organisation).
*/
@IsObject()
@IsNotEmpty()
parentGroup: RunnerOrganisation;
public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.parentGroup.id)) {
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup.id });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
return parentGroup;
}
throw new RunnerOrganisationWrongTypeError;
}
/**
* Creates a RunnerTeam entity from this.
*/
public async toRunnerTeam(): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam();
newRunnerTeam.id = this.id;
newRunnerTeam.name = this.name;
newRunnerTeam.parentGroup = await this.getParent();
newRunnerTeam.contact = await this.getContact()
return newRunnerTeam;
}
}

View File

@@ -0,0 +1,73 @@
import * as argon2 from "argon2";
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
import { JwtCreator } from '../../../jwtcreator';
import { User } from '../../entities/User';
import { ResponseAuth } from '../../responses/ResponseAuth';
/**
* This class is used to create auth credentials based on user credentials provided in a json body (post request).
* To be a little bit more exact: Is takes in a username/email + password and creates a new access and refresh token for the user.
* It of course checks for user existance, password validity and so on.
*/
export class CreateAuth {
/**
* The username of the user that want's to login.
* Either username or email have to be provided.
*/
@IsOptional()
@IsString()
username?: string;
/**
* The email address of the user that want's to login.
* Either username or email have to be provided.
*/
@IsOptional()
@IsEmail()
@IsString()
email?: string;
/**
* The user's password.
* Will be checked against an argon2 hash.
*/
@IsNotEmpty()
@IsString()
password: string;
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<ResponseAuth> {
let newAuth: ResponseAuth = new ResponseAuth();
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
}
if (!this.password) {
throw new PasswordNeededError();
}
const found_user = await getConnectionManager().get().getRepository(User).findOne({ relations: ['groups', 'permissions', 'groups.permissions'], where: [{ username: this.username }, { email: this.email }] });
if (!found_user) {
throw new UserNotFoundError();
}
if (found_user.enabled == false) { throw new UserDisabledError(); }
if (!(await argon2.verify(found_user.password, this.password + found_user.uuid))) {
throw new InvalidCredentialsError();
}
//Create the access token
const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry);
newAuth.access_token_expires_at = timestamp_accesstoken_expiry
//Create the refresh token
const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry);
newAuth.refresh_token_expires_at = timestamp_refresh_expiry
return newAuth;
}
}

View File

@@ -0,0 +1,52 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { DistanceDonation } from '../../entities/DistanceDonation';
import { Runner } from '../../entities/Runner';
import { CreateDonation } from './CreateDonation';
/**
* This class is used to create a new FixedDonation entity from a json body (post request).
*/
export class CreateDistanceDonation extends CreateDonation {
/**
* The donation's associated runner's id.
* This is important to link the runner's distance ran to the donation.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* The donation's amount per distance (full kilometer aka 1000 meters).
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amountPerDistance: number;
/**
* Creates a new FixedDonation entity from this.
*/
public async toEntity(): Promise<DistanceDonation> {
let newDonation = new DistanceDonation;
newDonation.amountPerDistance = this.amountPerDistance;
newDonation.donor = await this.getDonor();
newDonation.runner = await this.getRunner();
return newDonation;
}
/**
* Gets a runner based on the runner id provided via this.runner.
*/
public async getRunner(): Promise<Runner> {
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}

View File

@@ -0,0 +1,34 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation';
import { Donor } from '../../entities/Donor';
/**
* This class is used to create a new Donation entity from a json body (post request).
*/
export abstract class CreateDonation {
/**
* The donation's associated donor's id.
* This is important to link donations to donors.
*/
@IsInt()
@IsPositive()
donor: number;
/**
* Creates a new Donation entity from this.
*/
public abstract toEntity(): Promise<Donation>;
/**
* Gets a donor based on the donor id provided via this.donor.
*/
public async getDonor(): Promise<Donor> {
const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
if (!donor) {
throw new DonorNotFoundError();
}
return donor;
}
}

View File

@@ -0,0 +1,39 @@
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';
/**
* This classed is used to create a new Donor entity from a json body (post request).
*/
export class CreateDonor extends CreateParticipant {
/**
* Does this donor need a receipt?
*/
@IsBoolean()
@IsOptional()
receiptNeeded?: boolean = false;
/**
* Creates a new Donor entity from this.
*/
public async toEntity(): Promise<Donor> {
let newDonor: Donor = new Donor();
newDonor.firstname = this.firstname;
newDonor.middlename = this.middlename;
newDonor.lastname = this.lastname;
newDonor.phone = this.phone;
newDonor.email = this.email;
newDonor.receiptNeeded = this.receiptNeeded;
newDonor.address = this.address;
Address.validate(newDonor.address);
if (this.receiptNeeded == true && Address.isValidAddress(newDonor.address) == false) {
throw new DonorReceiptAddressNeededError()
}
return newDonor;
}
}

View File

@@ -0,0 +1,28 @@
import { IsInt, IsPositive } from 'class-validator';
import { FixedDonation } from '../../entities/FixedDonation';
import { CreateDonation } from './CreateDonation';
/**
* This class is used to create a new FixedDonation entity from a json body (post request).
*/
export class CreateFixedDonation extends CreateDonation {
/**
* The donation's amount.
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amount: number;
/**
* Creates a new FixedDonation entity from this.
*/
public async toEntity(): Promise<FixedDonation> {
let newDonation = new FixedDonation;
newDonation.amount = this.amount;
newDonation.donor = await this.getDonor();
return newDonation;
}
}

View File

@@ -0,0 +1,97 @@
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../../config';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { Address } from '../../entities/Address';
import { GroupContact } from '../../entities/GroupContact';
import { RunnerGroup } from '../../entities/RunnerGroup';
/**
* This classed is used to create a new GroupContact entity from a json body (post request).
*/
export class CreateGroupContact {
/**
* The new contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The new contact's middle name.
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The new contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The new contact's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* The contact's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The new contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* The new contacts's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* Get's all groups for this contact by their id's;
*/
public async getGroups(): Promise<RunnerGroup[]> {
if (!this.groups) { return null; }
let groups = new Array<RunnerGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
if (!found) { throw new RunnerGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
/**
* Creates a new GroupContact entity from this.
*/
public async toEntity(): Promise<GroupContact> {
let newContact: GroupContact = new GroupContact();
newContact.firstname = this.firstname;
newContact.middlename = this.middlename;
newContact.lastname = this.lastname;
newContact.email = this.email;
newContact.phone = this.phone;
newContact.address = this.address;
Address.validate(newContact.address);
newContact.groups = await this.getGroups();
return newContact;
}
}

View File

@@ -0,0 +1,53 @@
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { config } from '../../../config';
import { Address } from '../../entities/Address';
/**
* This classed is used to create a new Participant entity from a json body (post request).
*/
export abstract class CreateParticipant {
/**
* The new participant's first name.
*/
@IsString()
@IsNotEmpty()
firstname: string;
/**
* The new participant's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new participant's last name.
*/
@IsString()
@IsNotEmpty()
lastname: string;
/**
* The new participant's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsString()
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The new participant's e-mail address.
*/
@IsString()
@IsOptional()
@IsEmail()
email?: string;
/**
* The new participant's address.
*/
@IsOptional()
@IsObject()
address?: Address;
}

View File

@@ -0,0 +1,60 @@
import {
IsEnum,
IsInt,
IsNotEmpty
} from "class-validator";
import { getConnectionManager } from 'typeorm';
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
import { Permission } from '../../entities/Permission';
import { Principal } from '../../entities/Principal';
import { PermissionAction } from '../../enums/PermissionAction';
import { PermissionTarget } from '../../enums/PermissionTargets';
/**
* This classed is used to create a new Permission entity from a json body (post request).
*/
export class CreatePermission {
/**
* The new permissions's principal's id.
*/
@IsInt()
@IsNotEmpty()
principal: number;
/**
* The new permissions's target.
*/
@IsNotEmpty()
@IsEnum(PermissionTarget)
target: PermissionTarget;
/**
* The new permissions's action.
*/
@IsNotEmpty()
@IsEnum(PermissionAction)
action: PermissionAction;
/**
* Creates a new Permission entity from this.
*/
public async toEntity(): Promise<Permission> {
let newPermission: Permission = new Permission();
newPermission.principal = await this.getPrincipal();
newPermission.target = this.target;
newPermission.action = this.action;
return newPermission;
}
/**
* Gets the new permission's principal by it's id.
*/
public async getPrincipal(): Promise<Principal> {
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal })
if (!principal) { throw new PrincipalNotFoundError(); }
return principal;
}
}

View File

@@ -0,0 +1,50 @@
import { IsEmail, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
import { JwtCreator } from '../../../jwtcreator';
import { User } from '../../entities/User';
/**
* This calss 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()
@IsEmail()
@IsString()
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();
}
let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { 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(); }
found_user.refreshTokenCount = found_user.refreshTokenCount + 1;
found_user.resetRequestedTimestamp = Math.floor(Date.now() / 1000);
await getConnectionManager().get().getRepository(User).save(found_user);
//Create the reset token
let reset_token = JwtCreator.createReset(found_user);
return reset_token;
}
}

View File

@@ -1,12 +1,16 @@
import { IsInt } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner';
import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
/**
* This classed is used to create a new Runner entity from a json body (post request).
*/
export class CreateRunner extends CreateParticipant {
/**
@@ -16,9 +20,9 @@ export class CreateRunner extends CreateParticipant {
group: number;
/**
* Creates a Runner entity from this.
* Creates a new Runner entity from this.
*/
public async toRunner(): Promise<Runner> {
public async toEntity(): Promise<Runner> {
let newRunner: Runner = new Runner();
newRunner.firstname = this.firstname;
@@ -27,16 +31,17 @@ 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;
}
/**
* Manages all the different ways a group can be provided.
* Gets the new runner's group by it's id.
*/
public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined) {
if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.group)) {

View File

@@ -0,0 +1,45 @@
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { Runner } from '../../entities/Runner';
import { RunnerCard } from '../../entities/RunnerCard';
/**
* This classed is used to create a new RunnerCard entity from a json body (post request).
*/
export class CreateRunnerCard {
/**
* The card's associated runner's id.
*/
@IsInt()
@IsOptional()
runner?: number;
/**
* Is the new card enabled (for fraud reasons)?
* Default: true
*/
@IsBoolean()
enabled: boolean = true;
/**
* Creates a new RunnerCard entity from this.
*/
public async toEntity(): Promise<RunnerCard> {
let newCard: RunnerCard = new RunnerCard();
newCard.enabled = this.enabled;
newCard.runner = await this.getRunner();
return newCard;
}
public async getRunner(): Promise<Runner> {
if (!this.runner) { return null; }
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}

View File

@@ -0,0 +1,35 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { GroupContactNotFoundError } from '../../../errors/GroupContactErrors';
import { GroupContact } from '../../entities/GroupContact';
/**
* This classed is used to create a new RunnerGroup entity from a json body (post request).
*/
export abstract class CreateRunnerGroup {
/**
* The new group's name.
*/
@IsNotEmpty()
@IsString()
name: string;
/**
* The new group's contact's id.
* Optional
*/
@IsInt()
@IsOptional()
contact?: number;
/**
* Gets the new group's contact by it's id.
*/
public async getContact(): Promise<GroupContact> {
if (!this.contact) { return null; }
let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
if (!contact) { throw new GroupContactNotFoundError; }
return contact;
}
}

View File

@@ -0,0 +1,30 @@
import { IsObject, IsOptional } from 'class-validator';
import { Address } from '../../entities/Address';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This classed is used to create a new RunnerOrganisation entity from a json body (post request).
*/
export class CreateRunnerOrganisation extends CreateRunnerGroup {
/**
* The new organisation's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* Creates a new RunnerOrganisation entity from this.
*/
public async toEntity(): Promise<RunnerOrganisation> {
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
newRunnerOrganisation.name = this.name;
newRunnerOrganisation.contact = await this.getContact();
newRunnerOrganisation.address = this.address;
Address.validate(newRunnerOrganisation.address);
return newRunnerOrganisation;
}
}

View File

@@ -0,0 +1,45 @@
import { IsInt, IsNotEmpty } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError } from '../../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { RunnerTeam } from '../../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This classed is used to create a new RunnerTeam entity from a json body (post request).
*/
export class CreateRunnerTeam extends CreateRunnerGroup {
/**
* The new team's parent org's id.
*/
@IsInt()
@IsNotEmpty()
parentGroup: number;
/**
* Gets the new team's parent org based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> {
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();; }
return parentGroup;
}
/**
* Creates a new RunnerTeam entity from this.
*/
public async toEntity(): Promise<RunnerTeam> {
let newRunnerTeam: RunnerTeam = new RunnerTeam();
newRunnerTeam.name = this.name;
newRunnerTeam.parentGroup = await this.getParent();
newRunnerTeam.contact = await this.getContact()
return newRunnerTeam;
}
}

View File

@@ -0,0 +1,59 @@
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { Runner } from '../../entities/Runner';
import { Scan } from '../../entities/Scan';
/**
* This class is used to create a new Scan entity from a json body (post request).
*/
export abstract class CreateScan {
/**
* The scan's associated runner's id.
* This is important to link ran distances to runners.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* Is the scan valid (for fraud reasons).
* The determination of validity will work differently for every child class.
* Default: true
*/
@IsBoolean()
@IsOptional()
valid?: boolean = true;
/**
* The scan's distance in meters.
* Can be set manually or derived from another object.
*/
@IsInt()
@IsPositive()
public distance: number;
/**
* Creates a new Scan entity from this.
*/
public async toEntity(): Promise<Scan> {
let newScan = new Scan();
newScan.distance = this.distance;
newScan.valid = this.valid;
newScan.runner = await this.getRunner();
return newScan;
}
/**
* Gets a runner based on the runner id provided via this.runner.
*/
public async getRunner(): Promise<Runner> {
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}

View File

@@ -0,0 +1,64 @@
import * as argon2 from "argon2";
import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator';
import crypto from 'crypto';
import { getConnection } from 'typeorm';
import * as uuid from 'uuid';
import { TrackNotFoundError } from '../../../errors/TrackErrors';
import { ScanStation } from '../../entities/ScanStation';
import { Track } from '../../entities/Track';
/**
* This class is used to create a new StatsClient entity from a json body (post request).
*/
export class CreateScanStation {
/**
* The new station's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* The station's associated track's id.
*/
@IsInt()
@IsPositive()
track: number;
/**
* Is this station enabled?
*/
@IsBoolean()
@IsOptional()
enabled?: boolean = true;
/**
* Converts this to a ScanStation entity.
*/
public async toEntity(): Promise<ScanStation> {
let newStation: ScanStation = new ScanStation();
newStation.description = this.description;
newStation.enabled = this.enabled;
newStation.track = await this.getTrack();
let newUUID = uuid.v4().toUpperCase();
newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase();
newStation.key = await argon2.hash(newStation.prefix + "." + newUUID);
newStation.cleartextkey = newStation.prefix + "." + newUUID;
return newStation;
}
/**
* Get's a track by it's id provided via this.track.
* Used to link the new station to a track.
*/
public async getTrack(): Promise<Track> {
const track = await getConnection().getRepository(Track).findOne({ id: this.track });
if (!track) {
throw new TrackNotFoundError();
}
return track;
}
}

View File

@@ -0,0 +1,33 @@
import * as argon2 from "argon2";
import { IsOptional, IsString } from 'class-validator';
import crypto from 'crypto';
import * as uuid from 'uuid';
import { StatsClient } from '../../entities/StatsClient';
/**
* This classed is used to create a new StatsClient entity from a json body (post request).
*/
export class CreateStatsClient {
/**
* The new client's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* Converts this to a StatsClient entity.
*/
public async toEntity(): Promise<StatsClient> {
let newClient: StatsClient = new StatsClient();
newClient.description = this.description;
let newUUID = uuid.v4().toUpperCase();
newClient.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase();
newClient.key = await argon2.hash(newClient.prefix + "." + newUUID);
newClient.cleartextkey = newClient.prefix + "." + newUUID;
return newClient;
}
}

View File

@@ -0,0 +1,46 @@
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors';
import { Track } from '../../entities/Track';
/**
* This classed is used to create a new Track entity from a json body (post request).
*/
export class CreateTrack {
/**
* The new track's name.
*/
@IsString()
@IsNotEmpty()
name: string;
/**
* The new track's distance in meters (must be greater than 0).
*/
@IsInt()
@IsPositive()
distance: number;
/**
* The minimum time a runner should take to run a lap on this track (in seconds).
* Will be used for fraud detection.
*/
@IsInt()
@IsOptional()
minimumLapTime: number;
/**
* Creates a new Track entity from this.
*/
public toEntity(): Track {
let newTrack: Track = new Track();
newTrack.name = this.name;
newTrack.distance = this.distance;
newTrack.minimumLapTime = this.minimumLapTime;
if (this.minimumLapTime < 0) {
throw new TrackLapTimeCantBeNegativeError();
}
return newTrack;
}
}

View File

@@ -0,0 +1,79 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerCardNotFoundError } from '../../../errors/RunnerCardErrors';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors';
import { RunnerCard } from '../../entities/RunnerCard';
import { ScanStation } from '../../entities/ScanStation';
import { TrackScan } from '../../entities/TrackScan';
/**
* This classed is used to create a new Scan entity from a json body (post request).
*/
export class CreateTrackScan {
/**
* The id of the runnerCard associated with the scan.
* This get's saved for documentation and management purposes.
*/
@IsInt()
@IsPositive()
card: number;
/**
* The scanning station's id that created the scan.
* Mainly used for logging and traceing back scans (or errors).
*/
@IsInt()
@IsPositive()
station: number;
/**
* Creates a new Track entity from this.
*/
public async toEntity(): Promise<TrackScan> {
let newScan: TrackScan = new TrackScan();
newScan.station = await this.getStation();
newScan.card = await this.getCard();
newScan.track = newScan.station.track;
newScan.runner = newScan.card.runner;
if (!newScan.runner) {
throw new RunnerNotFoundError();
}
newScan.timestamp = Math.round(new Date().getTime() / 1000);
newScan.valid = await this.validateScan(newScan);
return newScan;
}
public async getCard(): Promise<RunnerCard> {
const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] });
if (!track) {
throw new RunnerCardNotFoundError();
}
return track;
}
public async getStation(): Promise<ScanStation> {
const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ["track"] });
if (!station) {
throw new ScanStationNotFoundError();
}
return station;
}
public async validateScan(scan: TrackScan): Promise<boolean> {
const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] });
if (scans.length == 0) { return true; }
const newestScan = scans[scans.length - 1];
if ((scan.timestamp - newestScan.timestamp) > scan.track.minimumLapTime) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,132 @@
import * as argon2 from "argon2";
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../../config';
import { UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
import { User } from '../../entities/User';
import { UserGroup } from '../../entities/UserGroup';
/**
* This classed is used to create a new User entity from a json body (post request).
*/
export class CreateUser {
/**
* The new user's first name.
*/
@IsString()
firstname: string;
/**
* The new user's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new user's last name.
*/
@IsString()
lastname: string;
/**
* The new user's username.
* You have to provide a email addres, so this is optional.
*/
@IsOptional()
@IsString()
username?: string;
/**
* The new user's email address.
*/
@IsEmail()
@IsString()
@IsNotEmpty()
email: string;
/**
* The new user's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsPhoneNumber(config.phone_validation_countrycode)
@IsOptional()
phone?: string;
/**
* The new user's password.
* This will of course not be saved in plaintext :)
*/
@IsString()
password: string;
/**
* Will the new user be enabled from the start?
* Default: true
*/
@IsBoolean()
@IsOptional()
enabled?: boolean = true;
/**
* The new user's groups' ids.
* You can provide either one groupId or an array of groupIDs.
*/
@IsOptional()
groups?: number[] | number
/**
* The user's profile pic (or rather a url pointing to it).
*/
@IsString()
@IsUrl()
@IsOptional()
profilePic?: string;
/**
* Converts this to a User entity.
*/
public async toEntity(): Promise<User> {
let newUser: User = new User();
if (!this.email) {
throw new UserEmailNeededError();
}
if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
newUser.email = this.email
newUser.username = this.username
newUser.firstname = this.firstname
newUser.middlename = this.middlename
newUser.lastname = this.lastname
newUser.uuid = uuid.v4()
newUser.phone = this.phone
newUser.password = await argon2.hash(this.password + newUser.uuid);
newUser.groups = await this.getGroups();
newUser.enabled = this.enabled;
if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
else { newUser.profilePic = this.profilePic; }
return newUser;
}
/**
* Get's all groups for this user by their id's;
*/
public async getGroups() {
if (!this.groups) { return null; }
let groups = new Array<UserGroup>();
if (!Array.isArray(this.groups)) {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
if (!found) { throw new UserGroupNotFoundError(); }
groups.push(found);
}
return groups;
}
}

View File

@@ -1,6 +1,9 @@
import { IsOptional, IsString } from 'class-validator';
import { UserGroup } from '../entities/UserGroup';
import { UserGroup } from '../../entities/UserGroup';
/**
* This classed is used to create a new UserGroup entity from a json body (post request).
*/
export class CreateUserGroup {
/**
* The new group's name.
@@ -17,9 +20,9 @@ export class CreateUserGroup {
description?: string;
/**
* Converts this to a UserGroup entity.
* Creates a new UserGroup entity from this.
*/
public async toUserGroup(): Promise<UserGroup> {
public async toEntity(): Promise<UserGroup> {
let newUserGroup: UserGroup = new UserGroup();
newUserGroup.name = this.name;

View File

@@ -0,0 +1,51 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { DistanceDonation } from '../../entities/DistanceDonation';
import { Runner } from '../../entities/Runner';
import { UpdateDonation } from './UpdateDonation';
/**
* This class is used to update a DistanceDonation entity (via put request).
*/
export class UpdateDistanceDonation extends UpdateDonation {
/**
* The donation's associated runner's id.
* This is important to link the runner's distance ran to the donation.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* The donation's amount per distance (full kilometer aka 1000 meters).
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amountPerDistance: number;
/**
* Update a DistanceDonation entity based on this.
* @param donation The donation that shall be updated.
*/
public async update(donation: DistanceDonation): Promise<DistanceDonation> {
donation.amountPerDistance = this.amountPerDistance;
donation.donor = await this.getDonor();
donation.runner = await this.getRunner();
return donation;
}
/**
* Gets a runner based on the runner id provided via this.runner.
*/
public async getRunner(): Promise<Runner> {
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
if (!runner) {
throw new RunnerNotFoundError();
}
return runner;
}
}

View File

@@ -0,0 +1,41 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation';
import { Donor } from '../../entities/Donor';
/**
* This class is used to update a Donation entity (via put request).
*/
export abstract class UpdateDonation {
/**
* The updated donation's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated donation's associated donor's id.
* This is important to link donations to donors.
*/
@IsInt()
@IsPositive()
donor: number;
/**
* Creates a new Donation entity from this.
*/
public abstract update(donation: Donation): Promise<Donation>;
/**
* Gets a donor based on the donor id provided via this.donor.
*/
public async getDonor(): Promise<Donor> {
const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
if (!donor) {
throw new DonorNotFoundError();
}
return donor;
}
}

View File

@@ -0,0 +1,46 @@
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';
/**
* This class is used to update a Donor entity (via put request).
*/
export class UpdateDonor extends CreateParticipant {
/**
* The updated donor'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;
/**
* Does the updated donor need a receipt?
*/
@IsBoolean()
@IsOptional()
receiptNeeded?: boolean;
/**
* Updates a provided Donor entity based on this.
*/
public async update(donor: Donor): Promise<Donor> {
donor.firstname = this.firstname;
donor.middlename = this.middlename;
donor.lastname = this.lastname;
donor.phone = this.phone;
donor.email = this.email;
donor.receiptNeeded = this.receiptNeeded;
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()
}
return donor;
}
}

View File

@@ -0,0 +1,27 @@
import { IsInt, IsPositive } from 'class-validator';
import { FixedDonation } from '../../entities/FixedDonation';
import { UpdateDonation } from './UpdateDonation';
/**
* This class is used to update a FixedDonation entity (via put request).
*/
export class UpdateFixedDonation extends UpdateDonation {
/**
* The updated donation's amount.
* The unit is your currency's smallest unit (default: euro cent).
*/
@IsInt()
@IsPositive()
amount: number;
/**
* Update a FixedDonation entity based on this.
* @param donation The donation that shall be updated.
*/
public async update(donation: FixedDonation): Promise<FixedDonation> {
donation.amount = this.amount;
donation.donor = await this.getDonor();
return donation;
}
}

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