Compare commits

...

534 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
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
201 changed files with 27287 additions and 1534 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,11 +22,11 @@
],
"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",
@@ -35,46 +35,64 @@
"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": [

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,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

@@ -10,7 +10,7 @@ import { RunnerController } from './RunnerController';
@Controller()
@Authorized(["RUNNER:IMPORT", "TEAM:IMPORT"])
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class ImportController {
private runnerController: RunnerController;
@@ -26,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>();
@@ -41,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)
}
@@ -62,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>();
@@ -84,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

@@ -3,8 +3,8 @@ import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors';
import { PrincipalNotFoundError } from '../errors/PrincipalErrors';
import { CreatePermission } from '../models/actions/CreatePermission';
import { UpdatePermission } from '../models/actions/UpdatePermission';
import { CreatePermission } from '../models/actions/create/CreatePermission';
import { UpdatePermission } from '../models/actions/update/UpdatePermission';
import { Permission } from '../models/entities/Permission';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponsePermission } from '../models/responses/ResponsePermission';
@@ -12,7 +12,7 @@ import { ResponsePrincipal } from '../models/responses/ResponsePrincipal';
@JsonController('/permissions')
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class PermissionController {
private permissionRepository: Repository<Permission>;
@@ -26,7 +26,7 @@ export class PermissionController {
@Get()
@Authorized("PERMISSION:GET")
@ResponseSchema(ResponsePermission, { isArray: true })
@OpenAPI({ description: 'Lists all permissions.' })
@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'] });
@@ -42,7 +42,7 @@ export class PermissionController {
@ResponseSchema(ResponsePermission)
@ResponseSchema(PermissionNotFoundError, { statusCode: 404 })
@OnUndefined(PermissionNotFoundError)
@OpenAPI({ description: 'Returns a permissions of a specified id (if it exists)' })
@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(); }
@@ -54,11 +54,11 @@ export class PermissionController {
@Authorized("PERMISSION:CREATE")
@ResponseSchema(ResponsePermission)
@ResponseSchema(PrincipalNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' })
@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.toPermission();
permission = await createPermission.toEntity();
} catch (error) {
throw error;
}
@@ -79,7 +79,7 @@ export class PermissionController {
@ResponseSchema(PrincipalNotFoundError, { statusCode: 404 })
@ResponseSchema(PermissionIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(PermissionNeedsPrincipalError, { statusCode: 406 })
@OpenAPI({ description: "Update a permission object (id can't be changed)." })
@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'] });
@@ -90,13 +90,13 @@ export class PermissionController {
if (oldPermission.id != permission.id) {
throw new PermissionIdsNotMatchingError();
}
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] });
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: await permission.getPrincipal() }, { relations: ['principal'] });
if (existingPermission) {
await this.remove(permission.id, true);
return new ResponsePermission(existingPermission);
}
await this.permissionRepository.save(await permission.updatePermission(oldPermission));
await this.permissionRepository.save(await permission.update(oldPermission));
return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
}
@@ -106,7 +106,7 @@ export class PermissionController {
@ResponseSchema(ResponsePermission)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a specified permission (if it exists).' })
@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; }

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 { 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')
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerController {
private runnerRepository: Repository<Runner>;
@@ -24,10 +27,10 @@ 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));
});
@@ -39,9 +42,9 @@ export class RunnerController {
@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);
}
@@ -51,17 +54,17 @@ export class RunnerController {
@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')
@@ -69,7 +72,7 @@ export class RunnerController {
@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'] });
@@ -81,25 +84,47 @@ export class RunnerController {
throw new RunnerIdsNotMatchingError();
}
await this.runnerRepository.save(await runner.updateRunner(oldRunner));
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
await this.runnerRepository.save(await runner.update(oldRunner));
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', '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

@@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
import { UpdateRunnerOrganisation } from '../models/actions/UpdateRunnerOrganisation';
import { 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')
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerOrganisationController {
private runnerOrganisationRepository: Repository<RunnerOrganisation>;
@@ -26,10 +26,10 @@ 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));
});
@@ -41,9 +41,9 @@ export class RunnerOrganisationController {
@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);
}
@@ -51,18 +51,18 @@ export class RunnerOrganisationController {
@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')
@@ -70,7 +70,7 @@ export class RunnerOrganisationController {
@ResponseSchema(ResponseRunnerOrganisation)
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a runnerOrganisation object (id can't be changed)." })
@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 });
@@ -82,9 +82,9 @@ export class RunnerOrganisationController {
throw new RunnerOrganisationIdsNotMatchingError();
}
await this.runnerOrganisationRepository.save(await updateOrganisation.updateRunnerOrganisation(oldRunnerOrganisation));
await this.runnerOrganisationRepository.save(await updateOrganisation.update(oldRunnerOrganisation));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] }));
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['contact', 'teams'] }));
}
@Delete('/:id')
@@ -94,11 +94,11 @@ export class RunnerOrganisationController {
@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

@@ -2,8 +2,8 @@ import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
import { UpdateRunnerTeam } from '../models/actions/UpdateRunnerTeam';
import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam';
import { RunnerTeam } from '../models/entities/RunnerTeam';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
@@ -11,7 +11,7 @@ import { RunnerController } from './RunnerController';
@JsonController('/teams')
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class RunnerTeamController {
private runnerTeamRepository: Repository<RunnerTeam>;
@@ -25,7 +25,7 @@ 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'] });
@@ -40,7 +40,7 @@ export class RunnerTeamController {
@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(); }
@@ -50,11 +50,11 @@ 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;
}
@@ -70,7 +70,7 @@ export class RunnerTeamController {
@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'] });
@@ -82,7 +82,7 @@ export class RunnerTeamController {
throw new RunnerTeamIdsNotMatchingError();
}
await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam));
await this.runnerTeamRepository.save(await runnerTeam.update(oldRunnerTeam));
return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
}
@@ -93,7 +93,7 @@ export class RunnerTeamController {
@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

@@ -1,12 +1,13 @@
import { Get, JsonController } from 'routing-controllers';
import { OpenAPI } from 'routing-controllers-openapi';
import { getConnection } from 'typeorm';
import { config } from '../config';
@JsonController('/status')
@JsonController()
export class StatusController {
@Get()
@OpenAPI({ description: "Lists all tracks." })
@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 {
@@ -19,4 +20,12 @@ export class StatusController {
"database connection": "✔"
};
}
@Get('/version')
@OpenAPI({ description: "A very basic endpoint that just returns the curent package version." })
getVersion() {
return {
"version": config.version
}
}
}

View File

@@ -1,15 +1,16 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { 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')
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class TrackController {
private trackRepository: Repository<Track>;
@@ -23,6 +24,7 @@ export class TrackController {
@Get()
@Authorized("TRACK:GET")
@ResponseSchema(ResponseTrack, { isArray: true })
@OpenAPI({ description: 'Lists all tracks.' })
async getAll() {
let responseTracks: ResponseTrack[] = new Array<ResponseTrack>();
const tracks = await this.trackRepository.find();
@@ -37,7 +39,7 @@ export class TrackController {
@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(); }
@@ -47,12 +49,13 @@ 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')
@@ -60,20 +63,21 @@ export class TrackController {
@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.save(track);
return new ResponseTrack(track);
return new ResponseTrack(await this.trackRepository.findOne({ id: id }));
}
@Delete('/:id')
@@ -81,11 +85,20 @@ export class TrackController {
@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,18 +1,19 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm';
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUser } from '../models/actions/CreateUser';
import { UpdateUser } from '../models/actions/UpdateUser';
import { CreateUser } from '../models/actions/create/CreateUser';
import { UpdateUser } from '../models/actions/update/UpdateUser';
import { User } from '../models/entities/User';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseUser } from '../models/responses/ResponseUser';
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
import { PermissionController } from './PermissionController';
@JsonController('/users')
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class UserController {
private userRepository: Repository<User>;
@@ -25,11 +26,11 @@ export class UserController {
@Get()
@Authorized("USER:GET")
@ResponseSchema(User, { isArray: true })
@OpenAPI({ description: 'Lists all users.' })
@ResponseSchema(ResponseUser, { isArray: true })
@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions granted to them.' })
async getAll() {
let responseUsers: ResponseUser[] = new Array<ResponseUser>();
const users = await this.userRepository.find({ relations: ['permissions', 'groups'] });
const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] });
users.forEach(user => {
responseUsers.push(new ResponseUser(user));
});
@@ -38,39 +39,53 @@ export class UserController {
@Get('/:id')
@Authorized("USER:GET")
@ResponseSchema(User)
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Returns a user of a specified id (if it exists)' })
@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that all permissions granted to the user will show up here.' })
async getOne(@Param('id') id: number) {
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUser(user);
}
@Get('/:id/permissions')
@Authorized("USER:GET")
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@OnUndefined(UserNotFoundError)
@OpenAPI({ description: 'Lists all permissions granted to the user sorted into directly granted and inherited as permission response objects.' })
async getPermissions(@Param('id') id: number) {
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
if (!user) { throw new UserNotFoundError(); }
return new ResponseUserPermissions(user);
}
@Post()
@Authorized("USER:CREATE")
@ResponseSchema(User)
@ResponseSchema(UserGroupNotFoundError)
@OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' })
@ResponseSchema(ResponseUser)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: 'Create a new user. <br> If you want to grant permissions to the user you have to create them seperately by posting to /api/permissions after creating the user.' })
async post(@Body({ validate: true }) createUser: CreateUser) {
let user;
try {
user = await createUser.toUser();
user = await createUser.toEntity();
} catch (error) {
throw error;
}
user = await this.userRepository.save(user)
return new ResponseUser(await this.userRepository.findOne(user, { relations: ['permissions', 'groups'] }));
return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Put('/:id')
@Authorized("USER:UPDATE")
@ResponseSchema(User)
@ResponseSchema(ResponseUser)
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
@OpenAPI({ description: "Update a user object (id can't be changed)." })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@OpenAPI({ description: "Update the user whose id you provided. <br> To change the permissions directly granted to the user please use /api/permissions instead. <br> Please remember that ids can't be changed." })
async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: id });
@@ -81,21 +96,23 @@ export class UserController {
if (oldUser.id != updateUser.id) {
throw new UserIdsNotMatchingError();
}
await this.userRepository.save(await updateUser.updateUser(oldUser));
await this.userRepository.save(await updateUser.update(oldUser));
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }));
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
}
@Delete('/:id')
@Authorized("USER:DELETE")
@ResponseSchema(User)
@ResponseSchema(ResponseUser)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a user runner (if it exists).' })
@OpenAPI({ description: 'Delete the user whose id you provided. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
if (!force) { throw new UserDeletionNotConfirmedError; }
let user = await this.userRepository.findOne({ id: id });
if (!user) { return null; }
const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] });;
const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
const permissionControler = new PermissionController();
for (let permission of responseUser.permissions) {

View File

@@ -3,7 +3,8 @@ 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';
@@ -11,7 +12,7 @@ import { PermissionController } from './PermissionController';
@JsonController('/usergroups')
@OpenAPI({ security: [{ "AuthToken": [] }] })
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
export class UserGroupController {
private userGroupsRepository: Repository<UserGroup>;
@@ -25,9 +26,9 @@ 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')
@@ -35,20 +36,20 @@ export class UserGroupController {
@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;
}
@@ -61,20 +62,20 @@ export class UserGroupController {
@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.save(userGroup);
return userGroup;
return (await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })).toResponse();
}
@Delete('/:id')
@@ -82,15 +83,15 @@ export class UserGroupController {
@ResponseSchema(ResponseUserGroup)
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
@OnUndefined(204)
@OpenAPI({ description: 'Delete a specified usergroup (if it exists).' })
@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 });
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 responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });
const permissionControler = new PermissionController();
const permissionController = new PermissionController();
for (let permission of responseGroup.permissions) {
await permissionControler.remove(permission.id, true);
await permissionController.remove(permission.id, true);
}
await this.userGroupsRepository.delete(group);

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

@@ -115,4 +115,26 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError {
@IsString()
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

@@ -13,12 +13,12 @@ export class PrincipalNotFoundError extends NotFoundError {
}
/**
* Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
* Error to throw, when a provided runner organization doesn't belong to the accepted types.
*/
export class PrincipalWrongTypeError extends NotAcceptableError {
@IsString()
name = "PrincipalWrongTypeError"
@IsString()
message = "The princial must have an existing principal's id. \n You provided a object of another type."
message = "The principal must have an existing principal's id. \n You provided a object of another type."
}

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

@@ -32,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

@@ -13,7 +13,7 @@ export class RunnerOrganisationNotFoundError extends NotFoundError {
}
/**
* Error to throw when two runner organisations' ids don't match.
* 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 {

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

@@ -22,4 +22,23 @@ export class TrackIdsNotMatchingError extends NotAcceptableError {
@IsString()
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

@@ -4,7 +4,7 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
/**
* Error to throw when no username or email is set.
* We somehow need to identify you :)
* We somehow need to identify you on login.
*/
export class UsernameOrEmailNeededError extends NotFoundError {
@IsString()
@@ -14,6 +14,30 @@ export class UsernameOrEmailNeededError extends NotFoundError {
message = "No username or email is set!"
}
/**
* Error to throw when no username contains illegal characters.
* Right now the only one is "@" but this could change in the future.
*/
export class UsernameContainsIllegalCharacterError extends NotAcceptableError {
@IsString()
name = "UsernameContainsIllegalCharacterError"
@IsString()
message = "The provided username contains illegal characters! \n Right now the following characters are considered illegal: '@'"
}
/**
* Error to throw when no email is set.
* We somehow need to identify you :)
*/
export class UserEmailNeededError extends NotFoundError {
@IsString()
name = "UserEmailNeededError"
@IsString()
message = "No email is set! \n You have to provide email addresses for users (used for password reset among others)."
}
/**
* Error to throw when a user couldn't be found.
*/
@@ -35,4 +59,16 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
@IsString()
message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
}
/**
* Error to throw when two users' ids don't match.
* Usually occurs when a user tries to change a user's id.
*/
export class UserDeletionNotConfirmedError extends NotAcceptableError {
@IsString()
name = "UserDeletionNotConfirmedError"
@IsString()
message = "You are trying to delete a user! \n If you're sure about doing this: provide the ?force=true query param."
}

View File

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

View File

@@ -33,6 +33,20 @@ export class JwtCreator {
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)
}
}
/**
@@ -92,23 +106,6 @@ export class JwtUser {
this.refreshTokenCount = user.refreshTokenCount;
this.uuid = user.uuid;
this.profilePic = user.profilePic;
this.permissions = this.getPermissions(user);
}
/**
* Handels getting the permissions granted to this user (direct or indirect).
* @param user User which's permissions shall be gotten.
*/
public getPermissions(user: User): string[] {
let returnPermissions: string[] = new Array<string>();
for (let permission of user.permissions) {
returnPermissions.push(permission.toString());
}
for (let group of user.groups) {
for (let permission of group.permissions) {
returnPermissions.push(permission.toString());
}
}
return Array.from(new Set(returnPermissions));
this.permissions = user.allPermissions;
}
}

View File

@@ -1,8 +1,8 @@
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 and swaggerUiExpress.
@@ -15,42 +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",
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
}
}
},
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,8 +1,8 @@
import { Request, Response } from 'express';
/**
* Custom express middleware that appends the raw body to the request obeject.
* Mainly used for parsing csvs from boddies.
* Custom express middleware that appends the raw body to the request object.
* Mainly used for parsing csvs from bodies.
*/
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {

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

@@ -2,13 +2,13 @@ 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, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError';
import { JwtCreator, JwtUser } from './jwtcreator';
import { User } from './models/entities/User';
import { config } from '../config';
import { IllegalJWTError, NoPermissionError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from '../errors/AuthError';
import { JwtCreator, JwtUser } from '../jwtcreator';
import { User } from '../models/entities/User';
/**
* Handels authorisation verification via jwt's for all api endpoints using the @Authorized decorator.
* Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
* @param permissions The permissions that the endpoint using @Authorized requires.
*/
@@ -31,6 +31,7 @@ const authchecker = async (action: Action, permissions: string[] | string) => {
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 = {}
@@ -42,7 +43,7 @@ const authchecker = async (action: Action, permissions: string[] | string) => {
}
/**
* Handels soft-refreshing of access-tokens.
* Handles soft-refreshing of access-tokens.
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
*/
const refresh = async (action: Action) => {
@@ -63,6 +64,7 @@ const refresh = async (action: Action) => {
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);

View File

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

View File

@@ -1,85 +0,0 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
import { GroupContact } from '../entities/GroupContact';
/**
* This classed is used to create a new Group entity from a json body (post request).
*/
export class CreateGroupContact {
/**
* The new contact's first name.
*/
@IsNotEmpty()
@IsString()
firstname: string;
/**
* The new contact's middle name.
*/
@IsOptional()
@IsString()
middlename?: string;
/**
* The new contact's last name.
*/
@IsNotEmpty()
@IsString()
lastname: string;
/**
* The new contact's address.
* Must be the address's id.
*/
@IsInt()
@IsOptional()
address?: number;
/**
* The contact's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The contact's email address.
*/
@IsOptional()
@IsEmail()
email?: string;
/**
* Gets the new contact's address by it's id.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined || this.address === null) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
/**
* Creates a new Address entity from this.
*/
public async toGroupContact(): Promise<GroupContact> {
let contact: GroupContact = new GroupContact();
contact.firstname = this.firstname;
contact.middlename = this.middlename;
contact.lastname = this.lastname;
contact.email = this.email;
contact.phone = this.phone;
contact.address = await this.getAddress();
return null;
}
}

View File

@@ -1,72 +0,0 @@
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
/**
* This classed is used to create a new Participant entity from a json body (post request).
*/
export abstract class CreateParticipant {
/**
* The new participant's first name.
*/
@IsString()
@IsNotEmpty()
firstname: string;
/**
* The new participant's middle name.
*/
@IsString()
@IsOptional()
middlename?: string;
/**
* The new participant's last name.
*/
@IsString()
@IsNotEmpty()
lastname: string;
/**
* The new participant's phone number.
* This will be validated against the configured country phone numer syntax (default: international).
*/
@IsString()
@IsOptional()
@IsPhoneNumber(config.phone_validation_countrycode)
phone?: string;
/**
* The new participant's e-mail address.
*/
@IsString()
@IsOptional()
@IsEmail()
email?: string;
/**
* The new participant's address.
* Must be of type number (address id).
*/
@IsInt()
@IsOptional()
address?: number;
/**
* Gets the new participant's address by it's address.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined || this.address === null) {
return null;
}
if (!isNaN(this.address)) {
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
if (!address) { throw new AddressNotFoundError; }
return address;
}
throw new AddressWrongTypeError;
}
}

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
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;
/**
* Creates a new Track entity from this.
*/
public toTrack(): Track {
let newTrack: Track = new Track();
newTrack.name = this.name;
newTrack.distance = this.distance;
return newTrack;
}
}

View File

@@ -5,7 +5,7 @@ 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.

View File

@@ -2,10 +2,10 @@ 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.
@@ -24,8 +24,8 @@ export class RefreshAuth {
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();
public async toAuth(): Promise<ResponseAuth> {
let newAuth: ResponseAuth = new ResponseAuth();
if (!this.token || this.token === undefined) {
throw new JwtNotProvidedError()
}
@@ -39,6 +39,7 @@ export class RefreshAuth {
if (!found_user) {
throw new UserNotFoundError()
}
if (found_user.enabled == false) { throw new UserDisabledError(); }
if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) {
throw new RefreshTokenCountInvalidError()
}

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,59 +0,0 @@
import { IsInt, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { CreateParticipant } from './CreateParticipant';
/**
* This class is used to update a Runner entity (via put request).
*/
export class UpdateRunner extends CreateParticipant {
/**
* The updated runner's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated runner's new team/org.
* Just has to contain the group's id -everything else won't be checked or changed.
*/
@IsObject()
group: RunnerGroup;
/**
* Updates a provided Runner entity based on this.
*/
public async updateRunner(runner: Runner): Promise<Runner> {
runner.firstname = this.firstname;
runner.middlename = this.middlename;
runner.lastname = this.lastname;
runner.phone = this.phone;
runner.email = this.email;
runner.group = await this.getGroup();
runner.address = await this.getAddress();
return runner;
}
/**
* Loads the updated runner's group based on it's id.
*/
public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.group.id)) {
let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group.id });
if (!group) { throw new RunnerGroupNotFoundError; }
return group;
}
throw new RunnerOrganisationWrongTypeError;
}
}

View File

@@ -1,52 +0,0 @@
import { IsInt, IsOptional } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { AddressNotFoundError } from '../../errors/AddressErrors';
import { Address } from '../entities/Address';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This class is used to update a RunnerOrganisation entity (via put request).
*/
export class UpdateRunnerOrganisation extends CreateRunnerGroup {
/**
* The updated orgs's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated organisation's address.
* Just has to contain the address's id - everything else won't be checked or changed.
* Optional.
*/
@IsInt()
@IsOptional()
address?: Address;
/**
* Loads the organisation's address based on it's id.
*/
public async getAddress(): Promise<Address> {
if (this.address === undefined || this.address === null) {
return null;
}
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address.id });
if (!address) { throw new AddressNotFoundError; }
return address;
}
/**
* Updates a provided RunnerOrganisation entity based on this.
*/
public async updateRunnerOrganisation(organisation: RunnerOrganisation): Promise<RunnerOrganisation> {
organisation.name = this.name;
organisation.contact = await this.getContact();
organisation.address = await this.getAddress();
return organisation;
}
}

View File

@@ -1,56 +0,0 @@
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
import { RunnerTeam } from '../entities/RunnerTeam';
import { CreateRunnerGroup } from './CreateRunnerGroup';
/**
* This class is used to update a RunnerTeam entity (via put request).
*/
export class UpdateRunnerTeam extends CreateRunnerGroup {
/**
* The updated team's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated team's parentGroup.
* Just has to contain the organisation's id - everything else won't be checked or changed.
*/
@IsObject()
@IsNotEmpty()
parentGroup: RunnerOrganisation;
/**
* Loads the updated teams's parentGroup based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError();
}
if (!isNaN(this.parentGroup.id)) {
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup.id });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
return parentGroup;
}
throw new RunnerOrganisationWrongTypeError;
}
/**
* Updates a provided RunnerTeam entity based on this.
*/
public async updateRunnerTeam(team: RunnerTeam): Promise<RunnerTeam> {
team.name = this.name;
team.parentGroup = await this.getParent();
team.contact = await this.getContact()
return team;
}
}

View File

@@ -1,11 +1,11 @@
import * as argon2 from "argon2";
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
import { JwtCreator } from '../../jwtcreator';
import { User } from '../entities/User';
import { Auth } from '../responses/ResponseAuth';
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
import { JwtCreator } from '../../../jwtcreator';
import { User } from '../../entities/User';
import { ResponseAuth } from '../../responses/ResponseAuth';
/**
* This class is used to create auth credentials based on user credentials provided in a json body (post request).
@@ -42,8 +42,8 @@ export class CreateAuth {
/**
* Creates a new auth object based on this.
*/
public async toAuth(): Promise<Auth> {
let newAuth: Auth = new Auth();
public async toAuth(): Promise<ResponseAuth> {
let newAuth: ResponseAuth = new ResponseAuth();
if (this.email === undefined && this.username === undefined) {
throw new UsernameOrEmailNeededError();
@@ -55,6 +55,7 @@ export class CreateAuth {
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();
}

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

@@ -4,11 +4,11 @@ import {
IsNotEmpty
} from "class-validator";
import { getConnectionManager } from 'typeorm';
import { PrincipalNotFoundError } from '../../errors/PrincipalErrors';
import { Permission } from '../entities/Permission';
import { Principal } from '../entities/Principal';
import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
import { Permission } from '../../entities/Permission';
import { Principal } from '../../entities/Principal';
import { PermissionAction } from '../../enums/PermissionAction';
import { PermissionTarget } from '../../enums/PermissionTargets';
/**
* This classed is used to create a new Permission entity from a json body (post request).
@@ -39,7 +39,7 @@ export class CreatePermission {
/**
* Creates a new Permission entity from this.
*/
public async toPermission(): Promise<Permission> {
public async toEntity(): Promise<Permission> {
let newPermission: Permission = new Permission();
newPermission.principal = await this.getPrincipal();

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,10 +1,11 @@
import { IsInt } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
import { Runner } from '../entities/Runner';
import { RunnerGroup } from '../entities/RunnerGroup';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { 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';
/**
@@ -21,7 +22,7 @@ export class CreateRunner extends CreateParticipant {
/**
* Creates a new Runner entity from this.
*/
public async toRunner(): Promise<Runner> {
public async toEntity(): Promise<Runner> {
let newRunner: Runner = new Runner();
newRunner.firstname = this.firstname;
@@ -30,7 +31,8 @@ export class CreateRunner extends CreateParticipant {
newRunner.phone = this.phone;
newRunner.email = this.email;
newRunner.group = await this.getGroup();
newRunner.address = await this.getAddress();
newRunner.address = this.address;
Address.validate(newRunner.address);
return newRunner;
}

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

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

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

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

View File

@@ -1,5 +1,5 @@
import { IsOptional, IsString } from 'class-validator';
import { UserGroup } from '../entities/UserGroup';
import { UserGroup } from '../../entities/UserGroup';
/**
* This classed is used to create a new UserGroup entity from a json body (post request).
@@ -22,7 +22,7 @@ export class CreateUserGroup {
/**
* Creates a new UserGroup entity from this.
*/
public async toUserGroup(): Promise<UserGroup> {
public async toEntity(): Promise<UserGroup> {
let newUserGroup: UserGroup = new UserGroup();
newUserGroup.name = this.name;

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

View File

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

View File

@@ -1,11 +1,11 @@
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
import { IsInt, IsNotEmpty, IsPositive } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { PermissionNeedsPrincipalError } from '../../errors/PermissionErrors';
import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../errors/PrincipalErrors';
import { Permission } from '../entities/Permission';
import { Principal } from '../entities/Principal';
import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets';
import { PermissionNeedsPrincipalError } from '../../../errors/PermissionErrors';
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
import { Permission } from '../../entities/Permission';
import { Principal } from '../../entities/Principal';
import { PermissionAction } from '../../enums/PermissionAction';
import { PermissionTarget } from '../../enums/PermissionTargets';
/**
* This class is used to update a Permission entity (via put request).
@@ -20,12 +20,11 @@ export class UpdatePermission {
id: number;
/**
* The updated permissions's principal.
* Just has to contain the principal's id -everything else won't be checked or changed.
* The updated permissions's principal's id.
*/
@IsObject()
@IsNotEmpty()
principal: Principal;
@IsInt()
@IsPositive()
principal: number;
/**
* The permissions's target.
@@ -42,7 +41,7 @@ export class UpdatePermission {
/**
* Updates a provided Permission entity based on this.
*/
public async updatePermission(permission: Permission): Promise<Permission> {
public async update(permission: Permission): Promise<Permission> {
permission.principal = await this.getPrincipal();
permission.target = this.target;
permission.action = this.action;
@@ -57,12 +56,8 @@ export class UpdatePermission {
if (this.principal === undefined || this.principal === null) {
throw new PermissionNeedsPrincipalError();
}
if (!isNaN(this.principal.id)) {
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal.id });
if (!principal) { throw new PrincipalNotFoundError(); }
return principal;
}
throw new PrincipalWrongTypeError();
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal });
if (!principal) { throw new PrincipalNotFoundError(); }
return principal;
}
}

View File

@@ -0,0 +1,57 @@
import { IsInt, IsPositive } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
import { Address } from '../../entities/Address';
import { Runner } from '../../entities/Runner';
import { RunnerGroup } from '../../entities/RunnerGroup';
import { CreateParticipant } from '../create/CreateParticipant';
/**
* This class is used to update a Runner entity (via put request).
*/
export class UpdateRunner extends CreateParticipant {
/**
* The updated runner's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated runner's group's id.
*/
@IsInt()
@IsPositive()
group: number;
/**
* Updates a provided Runner entity based on this.
*/
public async update(runner: Runner): Promise<Runner> {
runner.firstname = this.firstname;
runner.middlename = this.middlename;
runner.lastname = this.lastname;
runner.phone = this.phone;
runner.email = this.email;
runner.group = await this.getGroup();
if (!this.address) { runner.address.reset(); }
else { runner.address = this.address; }
Address.validate(runner.address);
return runner;
}
/**
* Loads the updated runner's group based on it's id.
*/
public async getGroup(): Promise<RunnerGroup> {
if (this.group === undefined || this.group === null) {
throw new RunnerTeamNeedsParentError();
}
let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group });
if (!group) { throw new RunnerGroupNotFoundError; }
return group;
}
}

View File

@@ -0,0 +1,51 @@
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { Runner } from '../../entities/Runner';
import { RunnerCard } from '../../entities/RunnerCard';
/**
* This class is used to update a RunnerCard entity (via put request).
*/
export class UpdateRunnerCard {
/**
* The updated card'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()
@IsPositive()
id?: number;
/**
* The updated card's associated runner's id.
*/
@IsInt()
@IsOptional()
runner?: number;
/**
* Is the updated card enabled (for fraud reasons)?
* Default: true
*/
@IsBoolean()
enabled: boolean = true;
/**
* Creates a new RunnerCard entity from this.
*/
public async update(card: RunnerCard): Promise<RunnerCard> {
card.enabled = this.enabled;
card.runner = await this.getRunner();
return card;
}
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,38 @@
import { IsInt, IsObject, IsOptional } from 'class-validator';
import { Address } from '../../entities/Address';
import { RunnerOrganisation } from '../../entities/RunnerOrganisation';
import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
/**
* This class is used to update a RunnerOrganisation entity (via put request).
*/
export class UpdateRunnerOrganisation extends CreateRunnerGroup {
/**
* The updated orgs's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated organisation's address.
*/
@IsOptional()
@IsObject()
address?: Address;
/**
* Updates a provided RunnerOrganisation entity based on this.
*/
public async update(organisation: RunnerOrganisation): Promise<RunnerOrganisation> {
organisation.name = this.name;
organisation.contact = await this.getContact();
if (!this.address) { organisation.address.reset(); }
else { organisation.address = this.address; }
Address.validate(organisation.address);
return organisation;
}
}

View File

@@ -0,0 +1,51 @@
import { IsInt, IsPositive } 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 '../create/CreateRunnerGroup';
/**
* This class is used to update a RunnerTeam entity (via put request).
*/
export class UpdateRunnerTeam extends CreateRunnerGroup {
/**
* The updated team's id.
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
*/
@IsInt()
id: number;
/**
* The updated team's parentGroup's id.
*/
@IsInt()
@IsPositive()
parentGroup: number;
/**
* Loads the updated teams's parentGroup based on it's id.
*/
public async getParent(): Promise<RunnerOrganisation> {
if (this.parentGroup === undefined || this.parentGroup === null) {
throw new RunnerTeamNeedsParentError();
}
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
return parentGroup;
}
/**
* Updates a provided RunnerTeam entity based on this.
*/
public async update(team: RunnerTeam): Promise<RunnerTeam> {
team.name = this.name;
team.parentGroup = await this.getParent();
team.contact = await this.getContact()
return team;
}
}

View File

@@ -0,0 +1,62 @@
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 update a Scan entity (via put request)
*/
export abstract class UpdateScan {
/**
* The updated scan'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 scan's associated runner's id.
* This is important to link ran distances to runners.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* Is the updated scan valid (for fraud reasons).
*/
@IsBoolean()
@IsOptional()
valid?: boolean = true;
/**
* The updated scan's distance in meters.
*/
@IsInt()
@IsPositive()
public distance: number;
/**
* Update a Scan entity based on this.
* @param scan The scan that shall be updated.
*/
public async update(scan: Scan): Promise<Scan> {
scan.distance = this.distance;
scan.valid = this.valid;
scan.runner = await this.getRunner();
return scan;
}
/**
* 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,39 @@
import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator';
import { ScanStation } from '../../entities/ScanStation';
/**
* This class is used to update a ScanStation entity (via put request)
*/
export class UpdateScanStation {
/**
* The updated station'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 station's description.
*/
@IsString()
@IsOptional()
description?: string;
/**
* Is this station enabled?
*/
@IsBoolean()
@IsOptional()
enabled?: boolean = true;
/**
* Update a ScanStation entity based on this.
* @param station The station that shall be updated.
*/
public async update(station: ScanStation): Promise<ScanStation> {
station.description = this.description;
station.enabled = this.enabled;
return station;
}
}

View File

@@ -0,0 +1,50 @@
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors';
import { Track } from '../../entities/Track';
/**
* This class is used to update a Track entity (via put request).
*/
export class UpdateTrack {
/**
* The updated track'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;
@IsString()
@IsNotEmpty()
name: string;
/**
* The updated 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;
/**
* Update a Track entity based on this.
* @param track The track that shall be updated.
*/
public async update(track: Track): Promise<Track> {
track.name = this.name;
track.distance = this.distance;
track.minimumLapTime = this.minimumLapTime;
if (this.minimumLapTime < 0) {
throw new TrackLapTimeCantBeNegativeError();
}
return track;
}
}

View File

@@ -0,0 +1,77 @@
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm';
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors';
import { Runner } from '../../entities/Runner';
import { ScanStation } from '../../entities/ScanStation';
import { TrackScan } from '../../entities/TrackScan';
/**
* This class is used to update a TrackScan entity (via put request)
*/
export abstract class UpdateTrackScan {
/**
* The updated scan'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 scan's associated runner's id.
* This is important to link ran distances to runners.
*/
@IsInt()
@IsPositive()
runner: number;
/**
* Is the updated scan valid (for fraud reasons).
*/
@IsBoolean()
@IsOptional()
valid?: boolean = true;
/**
* The updated scan's associated station's id.
* This is important to link ran distances to runners.
*/
@IsInt()
@IsPositive()
public station: number;
/**
* Update a TrackScan entity based on this.
* @param scan The scan that shall be updated.
*/
public async update(scan: TrackScan): Promise<TrackScan> {
scan.valid = this.valid;
scan.runner = await this.getRunner();
scan.station = await this.getStation();
scan.track = scan.station.track;
return scan;
}
/**
* 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;
}
/**
* Gets a runner based on the runner id provided via this.runner.
*/
public async getStation(): Promise<ScanStation> {
const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ['track'] });
if (!station) {
throw new ScanStationNotFoundError();
}
return station;
}
}

View File

@@ -1,11 +1,11 @@
import * as argon2 from "argon2";
import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import { config } from '../../config';
import { UsernameOrEmailNeededError } from '../../errors/AuthError';
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
import { User } from '../entities/User';
import { UserGroup } from '../entities/UserGroup';
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 class is used to update a User entity (via put request).
@@ -40,7 +40,7 @@ export class UpdateUser {
/**
* The updated user's username.
* You have to provide at least one of: {email, username}.
* You have to provide a email addres, so this is optional.
*/
@IsOptional()
@IsString()
@@ -48,12 +48,11 @@ export class UpdateUser {
/**
* The updated user's email address.
* You have to provide at least one of: {email, username}.
*/
@IsEmail()
@IsString()
@IsOptional()
email?: string;
@IsNotEmpty()
email: string;
/**
* The updated user's phone number.
@@ -77,42 +76,55 @@ export class UpdateUser {
* Should the user be enabled?
*/
@IsBoolean()
@IsOptional()
enabled: boolean = true;
/**
* The updated user's groups.
* This just has to contain the group's id - everything else won't be changed.
* The updated user's groups' ids.
*/
@IsOptional()
groups?: UserGroup[]
groups?: number | number[]
/**
* Updates a provided User entity based on this.
* The user's profile pic (or rather a url pointing to it).
*/
@IsString()
@IsUrl()
@IsOptional()
profilePic?: string;
/**
* Updates a user entity based on this.
* @param user The user that shall be updated.
*/
public async updateUser(user: User): Promise<User> {
user.email = this.email;
user.username = this.username;
if ((user.email === undefined || user.email === null) && (user.username === undefined || user.username === null)) {
throw new UsernameOrEmailNeededError();
public async update(user: User): Promise<User> {
if (!this.email) {
throw new UserEmailNeededError();
}
if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
if (this.password) {
user.password = await argon2.hash(this.password + user.uuid);
user.refreshTokenCount = user.refreshTokenCount + 1;
}
user.email = this.email;
user.username = this.username;
user.enabled = this.enabled;
user.firstname = this.firstname
user.middlename = this.middlename
user.lastname = this.lastname
user.phone = this.phone;
user.groups = await this.getGroups();
//TODO: ProfilePics
if (!this.profilePic) { user.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
else { user.profilePic = this.profilePic; }
return user;
}
/**
* Loads the updated user's groups based on their ids.
* Get's all groups for this user by their id's;
*/
public async getGroups() {
if (!this.groups) { return null; }
@@ -121,7 +133,7 @@ export class UpdateUser {
this.groups = [this.groups]
}
for (let group of this.groups) {
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id });
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
if (!found) { throw new UserGroupNotFoundError(); }
groups.push(found);
}

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