Compare commits

..

No commits in common. "main" and "v0.7.1" have entirely different histories.
main ... v0.7.1

70 changed files with 868 additions and 10164 deletions

View File

@ -19,24 +19,10 @@ get:
path: odit-git-bot path: odit-git-bot
name: sshkey name: sshkey
---
kind: secret
name: ci_token
get:
path: odit-ci-bot
name: apikey
---
kind: secret
name: npm_url
get:
path: odit-npm-cache
name: url
--- ---
kind: pipeline kind: pipeline
type: kubernetes type: kubernetes
name: tests:node name: tests:node_latest
clone: clone:
disable: true disable: true
steps: steps:
@ -46,14 +32,10 @@ steps:
- git clone $DRONE_REMOTE_URL . - git clone $DRONE_REMOTE_URL .
- git checkout $DRONE_SOURCE_BRANCH - git checkout $DRONE_SOURCE_BRANCH
- name: run tests - name: run tests
image: registry.odit.services/hub/library/node:19.5.0-alpine3.16 image: node:latest
commands: commands:
- npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8 - yarn
- pnpm i - yarn test:ci
- pnpm test:ci
environment:
NPM_REGISTRY_URL:
from_secret: npm_url
trigger: trigger:
event: event:
- pull_request - pull_request
@ -72,21 +54,53 @@ steps:
- git clone $DRONE_REMOTE_URL . - git clone $DRONE_REMOTE_URL .
- git checkout dev - git checkout dev
- name: build dev - name: build dev
depends_on: ["clone"] image: plugins/docker
image: registry.odit.services/library/drone-kaniko depends_on: [clone]
settings: settings:
username: username:
from_secret: docker_username from_secret: docker_username
password: password:
from_secret: docker_password from_secret: docker_password
build_args: repo: registry.odit.services/lfk/backend
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/backend
tags: tags:
- dev - dev
cache: true
registry: registry.odit.services registry: registry.odit.services
mtu: 1000
- name: run changelog export
depends_on: ["clone"]
image: node:latest
commands:
- npx auto-changelog --commit-limit false -p -u --hide-credit
- name: push new changelog to repo
depends_on: ["run changelog export"]
image: appleboy/drone-git-push
settings:
branch: dev
commit: true
commit_message: 🧾New changelog file version [CI SKIP] [skip ci]
author_email: bot@odit.services
remote: git@git.odit.services:lfk/backend.git
ssh_key:
from_secret: git_ssh
- name: run full license export
depends_on: ["clone"]
image: node: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: git_ssh
trigger: trigger:
branch: branch:
@ -111,20 +125,17 @@ steps:
- git checkout main - git checkout main
- name: build latest - name: build latest
depends_on: ["clone"] depends_on: ["clone"]
image: registry.odit.services/library/drone-kaniko image: plugins/docker
settings: settings:
username: username:
from_secret: docker_username from_secret: docker_username
password: password:
from_secret: docker_password from_secret: docker_password
build_args: repo: registry.odit.services/lfk/backend
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/backend
tags: tags:
- latest - latest
cache: true
registry: registry.odit.services registry: registry.odit.services
mtu: 1000
- name: push merge to repo - name: push merge to repo
depends_on: ["clone"] depends_on: ["clone"]
image: appleboy/drone-git-push image: appleboy/drone-git-push
@ -148,27 +159,30 @@ name: build:tags
steps: steps:
- name: build $DRONE_TAG - name: build $DRONE_TAG
depends_on: ["clone"] image: plugins/docker
image: registry.odit.services/library/drone-kaniko depends_on: [clone]
settings: settings:
username: username:
from_secret: docker_username from_secret: docker_username
password: password:
from_secret: docker_password from_secret: docker_password
build_args: repo: registry.odit.services/lfk/backend
- NPM_REGISTRY_URL:
from_secret: npm_url
repo: lfk/backend
tags: tags:
- "${DRONE_TAG}" - '${DRONE_TAG}'
cache: true
registry: registry.odit.services registry: registry.odit.services
mtu: 1000
- name: trigger node lib build
image: idcooldi/drone-webhook
settings:
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 - name: trigger js lib build
image: idcooldi/drone-webhook image: idcooldi/drone-webhook
settings: settings:
urls: https://ci.odit.services/api/repos/lfk/lfk-client-js/builds?SOURCE_TAG=${DRONE_TAG} urls: https://ci.odit.services/api/repos/lfk/lfk-client-js/builds?SOURCE_TAG=${DRONE_TAG}
bearer: bearer:
from_secret: ci_token from_secret: BOT_DRONE_KEY
trigger: trigger:
event: event:
- tag - tag

View File

@ -2,429 +2,16 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [v1.1.3](https://git.odit.services/lfk/backend/compare/v1.1.2...v1.1.3)
- feat(orgs): Also resolve child-teams' distances and add them to org total [`8d94186`](https://git.odit.services/lfk/backend/commit/8d9418635d3e381c0f55a2521a3334ba497c169a)
- fix(orgs): Removed unused log [`f2832a2`](https://git.odit.services/lfk/backend/commit/f2832a2daecc7bc7bbee4d4fceeab8db194730cf)
#### [v1.1.2](https://git.odit.services/lfk/backend/compare/v1.1.1...v1.1.2)
> 10 May 2023
- 🚀Bumped version to v1.1.2 [`0d21596`](https://git.odit.services/lfk/backend/commit/0d21596e2b64a99258d4925ae2ad627d5cdbd984)
- feat(groups): Resolve the total group distance on group get single (aka get org and get team) [`245827e`](https://git.odit.services/lfk/backend/commit/245827e9c659cf76183dc33ab253becc22ddf032)
- chore(package): Formatting [`4608a36`](https://git.odit.services/lfk/backend/commit/4608a36df6b187520ca0c331b8dce615205257be)
#### [v1.1.1](https://git.odit.services/lfk/backend/compare/v1.1.0...v1.1.1)
> 19 April 2023
- feat(donors): Resolve donations with donors via pagination [`12a9ae2`](https://git.odit.services/lfk/backend/commit/12a9ae24933117acb3ff9815a7d72abca5eea7a7)
- 🚀Bumped version to v1.1.1 [`cb1305a`](https://git.odit.services/lfk/backend/commit/cb1305aa77c36aa9d7900f09e7413bc6d45f2c89)
#### [v1.1.0](https://git.odit.services/lfk/backend/compare/v1.0.1...v1.1.0)
> 19 April 2023
- feat(stats): Added donation count and donor count to stats [`6f39ac4`](https://git.odit.services/lfk/backend/commit/6f39ac42dafc2a589bbb2256b0417f3e774ae174)
- 🚀Bumped version to v1.1.0 [`b9fe9f1`](https://git.odit.services/lfk/backend/commit/b9fe9f1c24653b91255a6dbbdc32c30b1b411eeb)
- Added average donation per distance to stats [`fe59e3a`](https://git.odit.services/lfk/backend/commit/fe59e3a557903cf555d4c50098e935c49ca1fac4)
- Added hints [`b25b0db`](https://git.odit.services/lfk/backend/commit/b25b0db76071ef8d50cc60e950a399dc060a2a9f)
- Added calls to controller [`6ee5328`](https://git.odit.services/lfk/backend/commit/6ee5328dbc404603d19db3a5173ae4def560a9c9)
- Formatting [`42c23a5`](https://git.odit.services/lfk/backend/commit/42c23a5883dacda4e0147842d448b3ad35b197b1)
#### [v1.0.1](https://git.odit.services/lfk/backend/compare/v1.0.0...v1.0.1)
> 18 April 2023
- fix(pagination) page=0 resulted in false thx JS [`fcee390`](https://git.odit.services/lfk/backend/commit/fcee3909f4c4664115cc7ecb94f30e0dd8e78ce0)
- 🚀Bumped version to v1.0.1 [`301f334`](https://git.odit.services/lfk/backend/commit/301f33467489a8533bdac11fbd10efd1b791f5e3)
### [v1.0.0](https://git.odit.services/lfk/backend/compare/v0.15.4...v1.0.0)
> 18 April 2023
- 🚀Bumped version to v1.0.0 [`f0e20e4`](https://git.odit.services/lfk/backend/commit/f0e20e413014fe446c97754d2765cdad92c2cc3b)
- Merge pull request 'feature/205-pagination' (#206) from feature/205-pagination into dev [`80de188`](https://git.odit.services/lfk/backend/commit/80de188565523d642407612272432ef07672b890)
- Added pagination for runner orgs [`538622a`](https://git.odit.services/lfk/backend/commit/538622aa1841e27256f304e15b4204c2f6d24d76)
- RunnerTeam Pagination [`0fa663a`](https://git.odit.services/lfk/backend/commit/0fa663a34104d438dd8fc9ab02458fdf289329f8)
- users pagination [`244da61`](https://git.odit.services/lfk/backend/commit/244da618926377f58bb12dbbd89b7bb39d84596e)
- Track pagination [`2a72aea`](https://git.odit.services/lfk/backend/commit/2a72aea10ef940fbdd4a9e6137b22933fdec7734)
- usergroup pagination [`513d7f6`](https://git.odit.services/lfk/backend/commit/513d7f6fbaebe39beab6ec95e6e42eb10c62296d)
- statsclient pagination [`71ebce6`](https://git.odit.services/lfk/backend/commit/71ebce6f8eebf110bb973a53b91dd6a49e1def99)
- scanstation pagination [`f60025b`](https://git.odit.services/lfk/backend/commit/f60025b6de79b0f5f89995bf59260194f5de9af0)
- Get all pagination for permissions [`86a21db`](https://git.odit.services/lfk/backend/commit/86a21dbfa4b50d8e80c611ea6e3eabfc2b8ae365)
- Pagination for group contacts [`1e9e24d`](https://git.odit.services/lfk/backend/commit/1e9e24d99d75ce6dc846ff662e62c886646ea974)
- Added pagination for get all donors [`4493c0e`](https://git.odit.services/lfk/backend/commit/4493c0e3d9beebbf7f601b39e1a2579771b4d152)
- Added pagination for donations [`f5d48fc`](https://git.odit.services/lfk/backend/commit/f5d48fc638080c9333efe474d86f131794c809af)
- Added pagination for runnercards [`b35a2dd`](https://git.odit.services/lfk/backend/commit/b35a2dd2fab708253373b3326f11ab574be18371)
- Added pagination for runners [`d873674`](https://git.odit.services/lfk/backend/commit/d873674819e6cb33cf89da4f8fdc30a0b41707e4)
- Added pagination for get all scans [`37b2ac9`](https://git.odit.services/lfk/backend/commit/37b2ac974b2276efd13538c127ba5ddda2537fe3)
- Updated test for attribute [`2f305e1`](https://git.odit.services/lfk/backend/commit/2f305e127c75e9e6ff8e9fc0cfc10cc3db44759d)
- Formatting [`a28ffe0`](https://git.odit.services/lfk/backend/commit/a28ffe06e5f3f69e4af6fdf0c66c9a1dfda10cfa)
#### [v0.15.4](https://git.odit.services/lfk/backend/compare/v0.15.3...v0.15.4)
> 15 April 2023
- Fixed possible null [`0f0c3c7`](https://git.odit.services/lfk/backend/commit/0f0c3c7214f357d991518aafd015ffc4d387ce59)
- 🚀Bumped version to v0.15.4 [`81aed1d`](https://git.odit.services/lfk/backend/commit/81aed1de40166f4cefabdb478d7638017127b25c)
#### [v0.15.3](https://git.odit.services/lfk/backend/compare/v0.15.2...v0.15.3)
> 15 April 2023
- Faster stats (not including donations) [`b2ac70e`](https://git.odit.services/lfk/backend/commit/b2ac70e0aec1064e54a5043a104e7892984b2338)
- 🚀Bumped version to v0.15.3 [`3909ed3`](https://git.odit.services/lfk/backend/commit/3909ed34f739e9fee90828f16757c75da90bab0f)
#### [v0.15.2](https://git.odit.services/lfk/backend/compare/v0.15.1...v0.15.2)
> 15 April 2023
- 🚀Bumped version to v0.15.2 [`5f17e7f`](https://git.odit.services/lfk/backend/commit/5f17e7f783a7e8e2efc8f7dbbf2c98bcd1d80240)
- Don't resolve runner group and parten with get all card requests [`2d8f752`](https://git.odit.services/lfk/backend/commit/2d8f7528d98144832e7609f5aa6fac8de4723c4a)
- Resolve groups again for card generation [`a5a56a2`](https://git.odit.services/lfk/backend/commit/a5a56a263a01dbd911a799ab57084166e17b80ac)
#### [v0.15.1](https://git.odit.services/lfk/backend/compare/v0.15.0...v0.15.1)
> 15 April 2023
- 🚀Bumped version to v0.15.1 [`9581185`](https://git.odit.services/lfk/backend/commit/9581185b24039338e7f238ecdcc3881bb5203759)
- Faster trackscan creation by only loading the latest scan [`e9914e3`](https://git.odit.services/lfk/backend/commit/e9914e317b7fd78863cfd8549bad65da9292b7ca)
- Log batch time in mass scan script [`2905884`](https://git.odit.services/lfk/backend/commit/2905884c024d7f275b3ad2c2858a2f0911adb95b)
- Dont load cards with get all runners request [`702070d`](https://git.odit.services/lfk/backend/commit/702070da669cc605b93e6f5b62d712c28f079dd0)
#### [v0.15.0](https://git.odit.services/lfk/backend/compare/v0.14.6...v0.15.0)
> 15 April 2023
- Added test script for creating mass scans [`8007117`](https://git.odit.services/lfk/backend/commit/80071174342d87199fcbd981cd8c92300b0a51e4)
- 🚀Bumped version to v0.15.0 [`cc89ba8`](https://git.odit.services/lfk/backend/commit/cc89ba8afb3120569613a889baf962555612e95a)
- Get all scans speed improvement [`23fa78e`](https://git.odit.services/lfk/backend/commit/23fa78eb9dcc01ecc036347f6703aacc0d163d7d)
- More scan request optimizations [`7c4ff42`](https://git.odit.services/lfk/backend/commit/7c4ff42a3b3e7b186e16c85a97d9ecc854a32cb0)
#### [v0.14.6](https://git.odit.services/lfk/backend/compare/v0.14.5...v0.14.6)
> 15 April 2023
- 🚀Bumped version to v0.14.6 [`3b3e689`](https://git.odit.services/lfk/backend/commit/3b3e68900beca16cfff88dbef22540f77750d29b)
- Missing orm file [`3ff666f`](https://git.odit.services/lfk/backend/commit/3ff666fd3e84ac8cf41b30e9e17082b10548d55b)
#### [v0.14.5](https://git.odit.services/lfk/backend/compare/v0.14.4...v0.14.5)
> 15 April 2023
- 🚀Bumped version to v0.14.5 [`4e44350`](https://git.odit.services/lfk/backend/commit/4e4435010fd7095e3b9742e207cba1b68cd6da3b)
- Entrypoint fix [`de9af5a`](https://git.odit.services/lfk/backend/commit/de9af5a90907dcfc9bfb1d5a56420eed8bb59922)
- Fixed copy [`ac631f0`](https://git.odit.services/lfk/backend/commit/ac631f0af467446552478873b7b4802a9310f865)
#### [v0.14.4](https://git.odit.services/lfk/backend/compare/v0.14.3...v0.14.4)
> 15 April 2023
- Switched ci over to pnpm + cache [`6275aaa`](https://git.odit.services/lfk/backend/commit/6275aaa326f1c02c8dd42aa31608978408c44ab7)
- 🚀Bumped version to v0.14.4 [`6bbdd5b`](https://git.odit.services/lfk/backend/commit/6bbdd5bb04a1c38e4b3a150db24b76e9c96490dd)
- Back to ean13 based codes [`a8fc755`](https://git.odit.services/lfk/backend/commit/a8fc7558408b97da4b2c469ae5e73ab502b4fda0)
- install prod in first step [`d027439`](https://git.odit.services/lfk/backend/commit/d02743984dfea8057be3081bd3a32a8f67e610aa)
- Switched dockerfile to pnpm 8 with cache [`93d43b7`](https://git.odit.services/lfk/backend/commit/93d43b76843d7cb411f37fd2066c6a5364c05415)
- COPY by stage name [`a64f6c9`](https://git.odit.services/lfk/backend/commit/a64f6c9822af2b927e91b0b55f1f50176de30169)
- pinned pnpm version [`2a94bfa`](https://git.odit.services/lfk/backend/commit/2a94bfa6227d14f635b5fc2789b59c36d490937e)
- custom pnpm cache [`85dc344`](https://git.odit.services/lfk/backend/commit/85dc3444acc677ddd242f9f2543ce477fe427a7c)
- added missing ci env [`734c826`](https://git.odit.services/lfk/backend/commit/734c826face58dd5c3bb2607bda6e7f6d051012e)
- pinned pnpm to 8 [`27e74e8`](https://git.odit.services/lfk/backend/commit/27e74e824cd1e23d4d53c1a983a1668dd87f5d59)
- coherent baseimage [`b5c0a28`](https://git.odit.services/lfk/backend/commit/b5c0a288ac3c020f5d753c558aee160fea0bae14)
- bumped final pnpm version [`33b25c9`](https://git.odit.services/lfk/backend/commit/33b25c9743abb7cefb3538f08cc2f78a646905c8)
#### [v0.14.3](https://git.odit.services/lfk/backend/compare/v0.14.2...v0.14.3)
> 18 March 2023
- 🚀Bumped version to v0.14.3 [`16ce0a8`](https://git.odit.services/lfk/backend/commit/16ce0a848050b74c4b6dd93f17e5a6e9024cdb7d)
- Adjusted modulo for new fixed card length [`9a8d618`](https://git.odit.services/lfk/backend/commit/9a8d618ae4584640e8be1ce9fe4bddd2ef7a92ae)
#### [v0.14.2](https://git.odit.services/lfk/backend/compare/v0.14.1...v0.14.2)
> 18 March 2023
- 🚀Bumped version to v0.14.2 [`38da2d3`](https://git.odit.services/lfk/backend/commit/38da2d33187f4b24eef878642e153663ecd95de1)
- Back to modulo [`068deb4`](https://git.odit.services/lfk/backend/commit/068deb4960bd16decf99887ffbda7a7d3dd9ff0b)
#### [v0.14.1](https://git.odit.services/lfk/backend/compare/v0.14.0...v0.14.1)
> 18 March 2023
- 🚀Bumped version to v0.14.1 [`13f093b`](https://git.odit.services/lfk/backend/commit/13f093bb6138a498f93a05ef6dd812ae92f2676a)
- Switched from card prefix replacement via modulo to regex [`6289f30`](https://git.odit.services/lfk/backend/commit/6289f307400aacaa9cfe03f3024c1e0d5554d4f2)
#### [v0.14.0](https://git.odit.services/lfk/backend/compare/v0.13.3...v0.14.0)
> 15 March 2023
- 🚀Bumped version to v0.14.0 [`6ff764b`](https://git.odit.services/lfk/backend/commit/6ff764bc340ca25b3bdd62c6892259e228723973)
- Updated default length [`ea87cc7`](https://git.odit.services/lfk/backend/commit/ea87cc793b163bf0d4405a25bbe83fbc8e31c206)
- breaking(runnercards): shorter runnercard codes (padding to 12 was a bit tooo ambitious) [`ffee887`](https://git.odit.services/lfk/backend/commit/ffee887ddf6a71102ee39533d7cd504d1fd6698f)
- Removed sqlite journal [`92517e3`](https://git.odit.services/lfk/backend/commit/92517e365393f4baac3814f5668874b5752dc7c8)
#### [v0.13.3](https://git.odit.services/lfk/backend/compare/v0.13.2...v0.13.3)
> 15 February 2023
- 🚀Bumped version to v0.13.3 [`3bac75e`](https://git.odit.services/lfk/backend/commit/3bac75e7ab9f16ecab1fbfa9915a7edb923883f6)
- Merge pull request 'feature/201-no_citizen-deletion' (#202) from feature/201-no_citizen-deletion into dev [`d05eddc`](https://git.odit.services/lfk/backend/commit/d05eddcae198427ce9a334096563b3aadcff2b56)
- Updated tests [`d5c689d`](https://git.odit.services/lfk/backend/commit/d5c689d6937288df7dca14ce26fbbd4f46a8752a)
- Added delete check for citizen org [`8fedd4e`](https://git.odit.services/lfk/backend/commit/8fedd4ef3bdd48dc42abc1d53006eefc145175e3)
#### [v0.13.2](https://git.odit.services/lfk/backend/compare/v0.13.1...v0.13.2)
> 3 February 2023
- 🚀Bumped version to v0.13.2 [`e8b2e6f`](https://git.odit.services/lfk/backend/commit/e8b2e6f26140a18c06b017e4461742d7e7942f08)
- Merge pull request 'move selfservice magic link endpoint to 15min rate limit' (#200) from feature/runner-selfservice-login-link-rate-limit into dev [`39f3b0e`](https://git.odit.services/lfk/backend/commit/39f3b0e01f03bfbcfcb0ea08d697268ce068e63d)
- move to 15min limit [`edaf255`](https://git.odit.services/lfk/backend/commit/edaf255e8f609185dcd6c2c0cd2e8b007b785e0c)
- Merge pull request 'Releases 0.12.0 and 0.13.0' (#199) from dev into main [`41c4ed4`](https://git.odit.services/lfk/backend/commit/41c4ed4d0faaed382801bbe480f31dafa6f3912d)
#### [v0.13.1](https://git.odit.services/lfk/backend/compare/v0.13.0...v0.13.1)
> 2 February 2023
- 🚀Bumped version to v0.13.1 [`f2bd88a`](https://git.odit.services/lfk/backend/commit/f2bd88aadfcb6ffa0485ea6afac8c7664a37f5f4)
- Updated description [`67a3661`](https://git.odit.services/lfk/backend/commit/67a36614485b2ea83c2de41e0684708b95a05b32)
#### [v0.13.0](https://git.odit.services/lfk/backend/compare/v0.12.0...v0.13.0)
> 2 February 2023
- Added faker for testing [`e184673`](https://git.odit.services/lfk/backend/commit/e1846739638905aab6ba7e059fd2cbf8ff467bf3)
- 📖New license file version [CI SKIP] [skip ci] [`2b641fa`](https://git.odit.services/lfk/backend/commit/2b641faa29c47d95f69983770dc4ab37e674604f)
- 🚀Bumped version to v0.13.0 [`0c763a2`](https://git.odit.services/lfk/backend/commit/0c763a2dfd39607b480d9aff7d3c883791f41700)
- Updated selfservice tests to prevent email duplication [`9bc80aa`](https://git.odit.services/lfk/backend/commit/9bc80aac8aab9b4dedc26c9bc3ce705d7fe9c0bf)
- Moved license and changelog export to releaseit hooks [`77c6303`](https://git.odit.services/lfk/backend/commit/77c6303014578edbbadeeaa790f7974bde2a9764)
- Updated readme [`4cdba8b`](https://git.odit.services/lfk/backend/commit/4cdba8bc77ce543f6fb636711b8728bce794eac7)
- 🧾New changelog file version [CI SKIP] [skip ci] [`ae14d6c`](https://git.odit.services/lfk/backend/commit/ae14d6c74f9205440b41ca5fdbd052ca449148fc)
- Added selfservice runner create check to prevent duplicate email [`68cd746`](https://git.odit.services/lfk/backend/commit/68cd746a9f3360b3630a9ba570213d2aa62497b4)
- Updated tests for new login in selfservice [`39aa759`](https://git.odit.services/lfk/backend/commit/39aa7598b7cd0ecb0f077f50ebdd31c6e205f06d)
- 🧾New changelog file version [CI SKIP] [skip ci] [`9fa8b93`](https://git.odit.services/lfk/backend/commit/9fa8b93c08ee52335b18e743f9d205b19e6095c6)
- Moved changelog generation to package script [`a7297ff`](https://git.odit.services/lfk/backend/commit/a7297ff933ae1372a9d508cdae1a54d2ebbcc647)
- Merge pull request 'feature/197-duplicate_runner_mail' (#198) from feature/197-duplicate_runner_mail into dev [`4b676bc`](https://git.odit.services/lfk/backend/commit/4b676bc85336c2d494e9e74823d38deec5cc0400)
- Updated logo url [`4433ddb`](https://git.odit.services/lfk/backend/commit/4433ddb1e15a35481728670e22049200644bf337)
- depends_on: ["clone"] [`9cc66ee`](https://git.odit.services/lfk/backend/commit/9cc66eebdfe8e7a2888bbc97197d1756ff44de30)
- Fixed typo [`19a290c`](https://git.odit.services/lfk/backend/commit/19a290c3a931ead0d9ae9ebb0985bfbaac54df59)
- Rename selfservice forgot to login [`69651d9`](https://git.odit.services/lfk/backend/commit/69651d9f6cd826b6d4720f164897a2a72a57c851)
- 📖New license file version [CI SKIP] [skip ci] [`6fd246f`](https://git.odit.services/lfk/backend/commit/6fd246f43cb3f4d0ccb6e017ee699889ba17daac)
- Add git for changelog fun [`2fa56b8`](https://git.odit.services/lfk/backend/commit/2fa56b82d1e082a1deae943e5fca5101f24e3ef5)
#### [v0.12.0](https://git.odit.services/lfk/backend/compare/v0.11.1...v0.12.0)
> 2 February 2023
- Pinned versions [`a6d5693`](https://git.odit.services/lfk/backend/commit/a6d5693ccdeb25b15a09af8f7438142114268807)
- Drone -> Kaniko based builds [`0e78951`](https://git.odit.services/lfk/backend/commit/0e789513008085d0db94fc3b2dd9e74a5e583049)
- Drone images to odit registry [`6ad56b3`](https://git.odit.services/lfk/backend/commit/6ad56b31269bf19a740c1b6b1a303a8a9d7d59d0)
- Bumped container base images [`d95c6d3`](https://git.odit.services/lfk/backend/commit/d95c6d33657f6aa977a8ebfefad7e199bb1cc9c3)
- Enabled tag via release script [`9217421`](https://git.odit.services/lfk/backend/commit/92174212213f874e41c9472a927bcf87b963ac94)
- Pinned pnpm for builds [`4570845`](https://git.odit.services/lfk/backend/commit/4570845b3e1bd00c228fe1b09b658c24e20aba7f)
- 🚀Bumped version to v0.12.0 [`4c10e20`](https://git.odit.services/lfk/backend/commit/4c10e20b91a8101ee37b230373ceb3e024582b41)
- Ignore pnpm lock [`1f2c8ab`](https://git.odit.services/lfk/backend/commit/1f2c8abb22f3ff1e61b7350b517bd699c3e315f6)
- 🧾New changelog file version [CI SKIP] [skip ci] [`31b258b`](https://git.odit.services/lfk/backend/commit/31b258b4ce82213144160a4233b7fd127e456776)
#### [v0.11.1](https://git.odit.services/lfk/backend/compare/v0.11.0...v0.11.1)
> 22 April 2021
- Merge pull request 'Release 0.11.1' (#196) from dev into main [`f19f280`](https://git.odit.services/lfk/backend/commit/f19f2808d88414f1877c01f10996dac68b6f9617)
- 🧾New changelog file version [CI SKIP] [skip ci] [`2229cdf`](https://git.odit.services/lfk/backend/commit/2229cdf20db1a98f9f76a99fa9d3f463cdf6d804)
- 🧾New changelog file version [CI SKIP] [skip ci] [`348fe52`](https://git.odit.services/lfk/backend/commit/348fe52c42cfa32239b703041820f725e147154e)
- Now prefixing runnercards with 2 [`8a82e05`](https://git.odit.services/lfk/backend/commit/8a82e059b74ceabf43c9cbfe9c9b89ef6ce15a28)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3b9cd2e`](https://git.odit.services/lfk/backend/commit/3b9cd2e1bbbe8e69c3883233a98f286d768c2b79)
- Added fix for the appended 2 [`eb526fb`](https://git.odit.services/lfk/backend/commit/eb526fb57faf631fd6e84af99af738ab1b3481c7)
- 🚀Bumped version to v0.11.1 [`95320ca`](https://git.odit.services/lfk/backend/commit/95320ca1bccc2886553accea6a428aadffda0a27)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f2d127f`](https://git.odit.services/lfk/backend/commit/f2d127fc98d75ce658424624abd382c087737ca0)
#### [v0.11.0](https://git.odit.services/lfk/backend/compare/v0.10.2...v0.11.0)
> 14 April 2021
- Merge pull request 'Release 0.11.0' (#195) from dev into main [`3220b19`](https://git.odit.services/lfk/backend/commit/3220b194d4c704835d6d106ec4d9d54a17a38b62)
- Fixed spelling [`da266a8`](https://git.odit.services/lfk/backend/commit/da266a8dd68dbb575997ae343624982b690486ec)
- Updated tests [`01ed514`](https://git.odit.services/lfk/backend/commit/01ed51489eb92fff907d46a930ecf0b0eb5cad2b)
- 🧾New changelog file version [CI SKIP] [skip ci] [`520608a`](https://git.odit.services/lfk/backend/commit/520608aef05b21f4daadf55cfc8caddba06b8f01)
- Added payedDonationAmount to donor and responsedonor [`8ae4b85`](https://git.odit.services/lfk/backend/commit/8ae4b8582749332f4fb081eee0c520293347001f)
- Responses now contain the donation status [`34dbaaa`](https://git.odit.services/lfk/backend/commit/34dbaaafe0422234848eabe3f52b26879c9e5a49)
- 🧾New changelog file version [CI SKIP] [skip ci] [`278c4a6`](https://git.odit.services/lfk/backend/commit/278c4a6a415434487a92ff66f8114bb2547aac48)
- Marked payedAmount as optional during creation and/or update [`0636616`](https://git.odit.services/lfk/backend/commit/0636616dad5afb41ffe47a857d91ac75b4f2f20a)
- Added payed amount fileld to donation class [`b8fbb72`](https://git.odit.services/lfk/backend/commit/b8fbb72fa0b659c9acc406c72a8a59c2174351b4)
- Added status to tests [`30c6d3d`](https://git.odit.services/lfk/backend/commit/30c6d3d8db9fe37a51e596a73add8b87e8616e54)
- Added payed amount to response class [`d64f470`](https://git.odit.services/lfk/backend/commit/d64f470b608b3f179ec77da0210de51c328ef3f2)
- 📖New license file version [CI SKIP] [skip ci] [`a2f0d81`](https://git.odit.services/lfk/backend/commit/a2f0d814fc782ad440500e7d6ec779b6ab7f0ac6)
- 🚀Bumped version to v0.11.0 [`3558e99`](https://git.odit.services/lfk/backend/commit/3558e9909088647bd4f1f4334f50c07a5ef00214)
- Merge pull request 'Donation payment management feature/193-donation_payments' (#194) from feature/193-donation_payments into dev [`6df5f63`](https://git.odit.services/lfk/backend/commit/6df5f634f3123e04c015889573ccc5674a8bab27)
- Added payed amount to crealte classes [`71542bc`](https://git.odit.services/lfk/backend/commit/71542bc3887b97c15436d03280e49f7b3f0fcb06)
- Added donation status enum [`b4c31ee`](https://git.odit.services/lfk/backend/commit/b4c31ee9b5b35d6e11b07f50f3d30ca12e0f7728)
- Added payed amount to update classes [`9930742`](https://git.odit.services/lfk/backend/commit/99307423c533f8cde847b59a80bffc2ff42c9769)
- 🧾New changelog file version [CI SKIP] [skip ci] [`0c61ff4`](https://git.odit.services/lfk/backend/commit/0c61ff457d02f750efa457dd75464187683b037a)
- Added mssing check to tests [`6c14ed9`](https://git.odit.services/lfk/backend/commit/6c14ed9c89eadc1a10db8c912d8ea2711a518766)
- No longer answering with null, but 0 [`49b174f`](https://git.odit.services/lfk/backend/commit/49b174f29f63e963e600d74b6923a20211d832eb)
- Saved missing file [`8fe3243`](https://git.odit.services/lfk/backend/commit/8fe32436935d7cd6c17eae1e138383d3b714e1ba)
#### [v0.10.2](https://git.odit.services/lfk/backend/compare/v0.10.1...v0.10.2)
> 7 April 2021
- Merge pull request 'Release 0.10.2' (#192) from dev into main [`1d82f65`](https://git.odit.services/lfk/backend/commit/1d82f65b0d3a32d10c1a10c991353c18696d58bf)
- Added first selfservice test [`057ae0d`](https://git.odit.services/lfk/backend/commit/057ae0d79758cd627d6d128406a0d201b6b7ad9b)
- 🧾New changelog file version [CI SKIP] [skip ci] [`b7ad5d3`](https://git.odit.services/lfk/backend/commit/b7ad5d3a31b8b4f5960852d3ac38af133719ebcd)
- First try of the laptime sort [`4471e57`](https://git.odit.services/lfk/backend/commit/4471e57438582d55ff846fd69c2cfcc26b40df2a)
- Potential fix for all remaining errors [`377d5da`](https://git.odit.services/lfk/backend/commit/377d5dadb2a14cb2d70e0b2dc77026f51b3fb51c)
- At least one fewer test should fail now [`87f444c`](https://git.odit.services/lfk/backend/commit/87f444c30d69d65a9f918c63631a859a389eeee3)
- Tried workaround for no availdable stats [`8f0f795`](https://git.odit.services/lfk/backend/commit/8f0f795a709db216396998b68b8bbd64ff4d44ff)
- Reverted temp bugfix [`4603a84`](https://git.odit.services/lfk/backend/commit/4603a84f16fb53a14d1792447100f5b470969dd0)
- Fixed sorting algo [`988f17a`](https://git.odit.services/lfk/backend/commit/988f17a795bb2d867e9d1d8e78051dff1a14ec30)
- Added runners stats tests [`7111068`](https://git.odit.services/lfk/backend/commit/7111068361e00cc1308664a3ae650a56e28c015c)
- Added basic laptime endpoint [`cb71fcd`](https://git.odit.services/lfk/backend/commit/cb71fcd13bc61e6214e2fd7b70e72094749463d3)
- Added orgs by donations stats tests [`d4a02e7`](https://git.odit.services/lfk/backend/commit/d4a02e7db2ff4976be21605e31aac2f3c82a49c0)
- Added teams stats endpoint tests [`b9a7dc8`](https://git.odit.services/lfk/backend/commit/b9a7dc84f05441445453193974b2a793b5197fa5)
- Now resolving all missing relations [`257f320`](https://git.odit.services/lfk/backend/commit/257f320ee3bf6429c4314c64023520366f9f730b)
- Added min laptime to StatsRunner [`51daf96`](https://git.odit.services/lfk/backend/commit/51daf969cf74792b2c2f2f16ce4359d9fca47bc8)
- Fixed sorting [`7b15c2d`](https://git.odit.services/lfk/backend/commit/7b15c2d88b14e7279aad97b0c950202ddb5acaaa)
- Fixed top-ten bein top 9 [`a6a526d`](https://git.odit.services/lfk/backend/commit/a6a526dc5d8b1613ea34e82e477081349e764aec)
- added new ci secret [`5633e85`](https://git.odit.services/lfk/backend/commit/5633e85f41cb69b10fd8a86f57f1bd2f50848f7b)
- Added temp console log for test [`22cae39`](https://git.odit.services/lfk/backend/commit/22cae39bd351ca285880e50187ea0d46a7a26437)
- 🧾New changelog file version [CI SKIP] [skip ci] [`610988e`](https://git.odit.services/lfk/backend/commit/610988ec16b8df61cca61cf2252a469d30318d81)
- Added temp console log for ci debugging [`4a73eab`](https://git.odit.services/lfk/backend/commit/4a73eab134c3a9f58771be996bc8811b62cf378e)
- Temp disabled runners by donations test [`0b07a53`](https://git.odit.services/lfk/backend/commit/0b07a53ed209c6193ead3c4d199545e22333ab32)
- Updated default docker-compose [`f8baca5`](https://git.odit.services/lfk/backend/commit/f8baca5ab2c56b906751bc7edd71477456ad91f3)
- 🧾New changelog file version [CI SKIP] [skip ci] [`dd6d799`](https://git.odit.services/lfk/backend/commit/dd6d799c847fc96aec1be8f2667ad371890076fb)
- Resolved missing parentgroup relation [`23bd432`](https://git.odit.services/lfk/backend/commit/23bd432c5f33a0863217120d97e2e4ea52a08baf)
- Removed console logs for now working tests [`71b33ab`](https://git.odit.services/lfk/backend/commit/71b33ab05b53b62c8b271bd2995c94b2fc212dfd)
- Fixed typo in test [`cbcb829`](https://git.odit.services/lfk/backend/commit/cbcb829fbde3a4a5e7f94de5dcf24d854c5fc257)
- Ptotential fix for stats failing [`dcdbdd1`](https://git.odit.services/lfk/backend/commit/dcdbdd15acfe6eef4220b7ed66db60d78107d1f9)
- 🚀Bumped version to v0.10.2 [`6e236ed`](https://git.odit.services/lfk/backend/commit/6e236ede145e164ee84543fb62404b4776550973)
- Merge pull request 'stats/runners/laptime feature/190-runners_laptime' (#191) from feature/190-runners_laptime into dev [`a694ad2`](https://git.odit.services/lfk/backend/commit/a694ad225c68fa23152402acba871c857433cc70)
- Removed all useless console.logs [`95e1eec`](https://git.odit.services/lfk/backend/commit/95e1eec313a79458dd75307a9d0f8319af0d0904)
- Pinned testing container tag to prod container tag [`10221b9`](https://git.odit.services/lfk/backend/commit/10221b9f2e4493080f3ff095d9772bcfd0ac50eb)
- Now resolving all relations for orgs by distance [`4a294b1`](https://git.odit.services/lfk/backend/commit/4a294b1e17c44294274b06748ec8141812c2d217)
- Added temp console log [`720774f`](https://git.odit.services/lfk/backend/commit/720774fcf47c38601ab88d5d74cfcd0e47b21acf)
- Removed console log for passing tests [`132b48c`](https://git.odit.services/lfk/backend/commit/132b48cf2a9e990a5e830c744ed8244bd25e8b3a)
- Removed console log [`1d8c8c8`](https://git.odit.services/lfk/backend/commit/1d8c8c8e9cefa58449f7abb2481d9396fe37ba20)
- Temp test logging workaround [`bf686e8`](https://git.odit.services/lfk/backend/commit/bf686e89e02998ccc80c838ef890c736c252634c)
- Temp test logging workaround [`6163f0a`](https://git.odit.services/lfk/backend/commit/6163f0a90b3721d3a1488f89cbb39ddff7152241)
- Removed test for content type [`63964fb`](https://git.odit.services/lfk/backend/commit/63964fbf2c41d9b90f995f056e9db65ab07d54a8)
#### [v0.10.1](https://git.odit.services/lfk/backend/compare/v0.10.0...v0.10.1)
> 3 April 2021
- Merge pull request 'Release 0.10.1' (#189) from dev into main [`e89e07d`](https://git.odit.services/lfk/backend/commit/e89e07d0fc99f14148b01204fb8ed39e2da77e38)
- 🧾New changelog file version [CI SKIP] [skip ci] [`69afd4d`](https://git.odit.services/lfk/backend/commit/69afd4d5877401eb46df430f43a7feb273abda1e)
- 🚀Bumped version to v0.10.1 [`24d152f`](https://git.odit.services/lfk/backend/commit/24d152fdc8fe17fffa2f2a718d7145ba8a91d79c)
- New class: ResponseSelfServiceDonor [`d70c5b1`](https://git.odit.services/lfk/backend/commit/d70c5b1bbc9f02782f8755b6929e2d3458e10221)
- 🧾New changelog file version [CI SKIP] [skip ci] [`4279e43`](https://git.odit.services/lfk/backend/commit/4279e4374304887e8db40eab77763b20bbce91a1)
- Removed duplicate openapi statement [`4834a66`](https://git.odit.services/lfk/backend/commit/4834a6698b0958602421c1478a95fec7edda910b)
- Switched selfservice donation.donor from string to object [`0767943`](https://git.odit.services/lfk/backend/commit/0767943721b6964d542f580c541e744f86444ac6)
- Adjusted runner property names [`ca87774`](https://git.odit.services/lfk/backend/commit/ca87774767807a2c4bc869b0de95cc73832a8405)
- 🧾New changelog file version [CI SKIP] [skip ci] [`71e3d0e`](https://git.odit.services/lfk/backend/commit/71e3d0efe2cbde47aea0f26cb5a8b5cd3312707d)
- 🧾New changelog file version [CI SKIP] [skip ci] [`c28843c`](https://git.odit.services/lfk/backend/commit/c28843c405dc4fd06a10f0fb85814acede15a769)
- Merge pull request 'Selfservice donations reformatting feature/187-selfservice_donation' (#188) from feature/187-selfservice_donation into dev [`d837654`](https://git.odit.services/lfk/backend/commit/d837654617f7de5d055ffb06c65e2cd52f65c604)
- Added new responsetype for new class [`f693f2c`](https://git.odit.services/lfk/backend/commit/f693f2cde9a04147155aea4de5d52e1d19d722ca)
#### [v0.10.0](https://git.odit.services/lfk/backend/compare/v0.9.2...v0.10.0)
> 1 April 2021
- Merge pull request 'Release 0.10.0' (#186) from dev into main [`b517dff`](https://git.odit.services/lfk/backend/commit/b517dff8a82c960836d9f0be90fd89f3ba2fae7d)
- 🚀Bumped version to v0.10.0 [`dc3071f`](https://git.odit.services/lfk/backend/commit/dc3071f7d2be298f0bb02d86ec67ed1125cd3b49)
- Added locale to mail related runner endpoints [`7af883f`](https://git.odit.services/lfk/backend/commit/7af883f27198206af542bcaff4686221d3788e87)
- Added locale to mail related runner endpoints [`f543307`](https://git.odit.services/lfk/backend/commit/f5433076b01c743ed9af085fccadb8f1edc26419)
- 🧾New changelog file version [CI SKIP] [skip ci] [`5fb355f`](https://git.odit.services/lfk/backend/commit/5fb355f450f19e96d3671b1a46e94d564495942b)
- 🧾New changelog file version [CI SKIP] [skip ci] [`114c246`](https://git.odit.services/lfk/backend/commit/114c246aceba566cc0dd6daab51a77b951b031cc)
- Merge pull request 'Mail locales feature/184-mail_locales' (#185) from feature/184-mail_locales into dev [`33c13de`](https://git.odit.services/lfk/backend/commit/33c13de32c68a3d9e87e4fd9ad12a815ed8c9fde)
- Added locale to mail related user endpoints [`1be073a`](https://git.odit.services/lfk/backend/commit/1be073a4fa39f0332a46f567ee6af10a9137844c)
- 🧾New changelog file version [CI SKIP] [skip ci] [`6aafe4a`](https://git.odit.services/lfk/backend/commit/6aafe4a6ae7d253ab39220e551c52ae067cc481a)
#### [v0.9.2](https://git.odit.services/lfk/backend/compare/v0.9.1...v0.9.2)
> 29 March 2021
- Merge pull request 'Release 0.9.2' (#183) from dev into main [`bdeeb03`](https://git.odit.services/lfk/backend/commit/bdeeb036459c2a2131e843d8a5a6b338e0ba46ea)
- 🧾New changelog file version [CI SKIP] [skip ci] [`675c876`](https://git.odit.services/lfk/backend/commit/675c8762e8e4cf28d2f334d5ab2e1cb6b594e33c)
- Fixed bug in return creation [`6c9b91d`](https://git.odit.services/lfk/backend/commit/6c9b91d75a0d08fc4ab0e72c7a09bd0133566368)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8c00aef`](https://git.odit.services/lfk/backend/commit/8c00aefd6ce3723d9f83d1c94e6491d5d597391f)
- 🚀Bumped version to v0.9.2 [`89e3924`](https://git.odit.services/lfk/backend/commit/89e392473c52a3f328545699a0f4df89be33ba89)
#### [v0.9.1](https://git.odit.services/lfk/backend/compare/v0.9.0...v0.9.1)
> 29 March 2021
- Merge pull request 'Release v0.9.1' (#182) from dev into main [`3afd785`](https://git.odit.services/lfk/backend/commit/3afd785a54fac91c12af789af19b45e6124e0e39)
- 🚀Bumped version to v0.9.1 [`a139554`](https://git.odit.services/lfk/backend/commit/a139554e059e9a10acb1733ce1a82b610cc99269)
- 🧾New changelog file version [CI SKIP] [skip ci] [`8099999`](https://git.odit.services/lfk/backend/commit/8099999e2cdfc8046f9ff4a90681281b671e402d)
- 🧾New changelog file version [CI SKIP] [skip ci] [`0290b0e`](https://git.odit.services/lfk/backend/commit/0290b0e5f531364d37d8157e639614cf5a6b4189)
- Merge pull request 'Return cards generated in bulk feature/180-blank_generation_return' (#181) from feature/180-blank_generation_return into dev [`0f7fa99`](https://git.odit.services/lfk/backend/commit/0f7fa990d473ce2dce032c47c39f79c1d0e8df90)
- Added query param to return created runenrcards [`5a36c8d`](https://git.odit.services/lfk/backend/commit/5a36c8dcae3d79b3b05ffb30a7ebb0d31dc8183a)
- 🧾New changelog file version [CI SKIP] [skip ci] [`58f4d21`](https://git.odit.services/lfk/backend/commit/58f4d2151f459bc72692cc70e02a59b77abfb9f0)
- Added test for returnCards=true array length [`1cb2dc9`](https://git.odit.services/lfk/backend/commit/1cb2dc9d53b530435f5798f9cdf7ee866eb7416e)
- Added test for single card generation with returnCards=true [`6005b06`](https://git.odit.services/lfk/backend/commit/6005b0661f1d5c461bb102e243cc209a8adc21fa)
- Fixed copy-paste oversight [`2f568c9`](https://git.odit.services/lfk/backend/commit/2f568c9cb8ae39ce40ec8df6d9acbaf0d5ae1a26)
#### [v0.9.0](https://git.odit.services/lfk/backend/compare/v0.8.0...v0.9.0)
> 26 March 2021
- Merge pull request 'Release 0.9.0' (#179) from dev into main [`95135dd`](https://git.odit.services/lfk/backend/commit/95135ddc893dcf64be67b47b0ef2b0d9041253bd)
- Reenabled user tests [`4c66650`](https://git.odit.services/lfk/backend/commit/4c6665062fe6717242e43b58e66c1f1d030c018d)
- Moved to tmp files to better check for other problems [`7a64f23`](https://git.odit.services/lfk/backend/commit/7a64f2393783f97a9729356bc1dfd831927dd312)
- Added user creation invalid tests [`888cab5`](https://git.odit.services/lfk/backend/commit/888cab5898caf9e552c421346934bf90f717a653)
- Updated auth test to comply with the new pw requirements [`63f6526`](https://git.odit.services/lfk/backend/commit/63f6526e4f59621edbf1fad59fc569b4bd6acbf2)
- Added user deletion tests [`e6a8ebc`](https://git.odit.services/lfk/backend/commit/e6a8ebcb5b4f430254da4afe159141b21d8da0ed)
- Added user creation valid tests [`383a809`](https://git.odit.services/lfk/backend/commit/383a8095b8286d51fb2fb24ae2fd0156230e56ab)
- 📖New license file version [CI SKIP] [skip ci] [`bd7b81e`](https://git.odit.services/lfk/backend/commit/bd7b81efe795c02512c87f3b5dd5eec796580144)
- Added password errors [`24c38cc`](https://git.odit.services/lfk/backend/commit/24c38cce26da41ccf375e1ccf04afa1868aad8df)
- 🧾New changelog file version [CI SKIP] [skip ci] [`274a146`](https://git.odit.services/lfk/backend/commit/274a146b9bccfe5e1a879ca137ebb4f51eaa5d57)
- Fixed test params [`070560e`](https://git.odit.services/lfk/backend/commit/070560e8632e833dd26505c02ccb2474462b63ac)
- No longer using createuser in seeding process [`96ba25e`](https://git.odit.services/lfk/backend/commit/96ba25ec6c6c397cd2aa322afa79024395f658fe)
- 🧾New changelog file version [CI SKIP] [skip ci] [`a7fe1e1`](https://git.odit.services/lfk/backend/commit/a7fe1e175918edd7a98983ece570b47075e85e9a)
- 🚀Bumped version to v0.8.0 [`c23b4d9`](https://git.odit.services/lfk/backend/commit/c23b4d907f20ed7af37a6de6ea4c61433e30b29b)
- 🚀Bumped version to v0.9.0 [`56a5f41`](https://git.odit.services/lfk/backend/commit/56a5f4168621263daeab5d2fda97b944cdc6ab31)
- Merge pull request 'Password security feature/99-password_checks' (#177) from feature/99-password_checks into dev [`5a3fc5b`](https://git.odit.services/lfk/backend/commit/5a3fc5b2bd06b3e26177d017d3503f4f627be3f2)
- Added pw errors to user controller [`b24e24f`](https://git.odit.services/lfk/backend/commit/b24e24ff7dd75d972cdab0fd1e2fe6c532ca2b2f)
- Now checking password rules on user creation [`5daaa3a`](https://git.odit.services/lfk/backend/commit/5daaa3a73c4eca2817d67e226679d125928a3645)
- Now checking password rules on user update [`48a87e8`](https://git.odit.services/lfk/backend/commit/48a87e8936e13c48f4baa3f4b10f781ad2f55a44)
- Fixed pw not getting hashed currectly; [`cb3ea9b`](https://git.odit.services/lfk/backend/commit/cb3ea9b1ebb82c650abd83d4be8629cfe29a5b21)
- Added pw errors to me controller [`9ce35d8`](https://git.odit.services/lfk/backend/commit/9ce35d8eb78a01f40af8c70e640eca3bcb142304)
- Now forceing user deletion in tests [`8154e71`](https://git.odit.services/lfk/backend/commit/8154e715bbf18938bd5d1031656a88d39231fa81)
- Added password checker dependency [`bd00f4f`](https://git.odit.services/lfk/backend/commit/bd00f4f8d585fb6878874810f7de0b8b9f3950d5)
- Fixed empty object getting called [`5369000`](https://git.odit.services/lfk/backend/commit/536900091afd7366128f21058490d0d4f15c6c89)
- 🧾New changelog file version [CI SKIP] [skip ci] [`03d76e6`](https://git.odit.services/lfk/backend/commit/03d76e6d0bc5b4655f7f441232681c9462815526)
- Formatting [`b8c28eb`](https://git.odit.services/lfk/backend/commit/b8c28ebb0808395218b5fb9031f477ae1d48e65e)
#### [v0.8.0](https://git.odit.services/lfk/backend/compare/v0.7.1...v0.8.0)
> 26 March 2021
- Merge pull request 'Release 0.8.0' (#176) from dev into main [`3f8e8ce`](https://git.odit.services/lfk/backend/commit/3f8e8ce3a66a943801c0c8e17885e71feeee744f)
- 🧾New changelog file version [CI SKIP] [skip ci] [`c9bd6de`](https://git.odit.services/lfk/backend/commit/c9bd6de4762fec04e1e02cd3b667838d05ef39a7)
- Merge pull request 'Selfservice deletion feature/174-selfservice_deletion' (#175) from feature/174-selfservice_deletion into dev [`e702118`](https://git.odit.services/lfk/backend/commit/e702118d4d80e362e41bb88c74343d50530d1338)
- Added tests for the new endpoint [`20aeed8`](https://git.odit.services/lfk/backend/commit/20aeed87780247dc6401bba725801fc1874e50b5)
- Removed param from test [`97159dd`](https://git.odit.services/lfk/backend/commit/97159dd9f81aed080c174a3eb8da9e66dfea9b10)
- Added selfservice deletion endpoint [`dcb12b0`](https://git.odit.services/lfk/backend/commit/dcb12b0ac289f8df148ba10ae6389727c16f53fd)
- 🧾New changelog file version [CI SKIP] [skip ci] [`88844e1`](https://git.odit.services/lfk/backend/commit/88844e1a44d87a7dc253bf9aedf2fb3f6cdd1cfe)
- Fixed response bug [`ccb7ae2`](https://git.odit.services/lfk/backend/commit/ccb7ae29a39387c0f2762861565dc22996a2493a)
- Updated old hint [`dd12583`](https://git.odit.services/lfk/backend/commit/dd1258333ef67243f8a8df97c176ec5a054a5e3b)
#### [v0.7.1](https://git.odit.services/lfk/backend/compare/v0.7.0...v0.7.1) #### [v0.7.1](https://git.odit.services/lfk/backend/compare/v0.7.0...v0.7.1)
> 26 March 2021
- Merge pull request 'Release 0.7.1' (#173) from dev into main [`e76a9ce`](https://git.odit.services/lfk/backend/commit/e76a9cef956b00de7bbb11b6d863d4f33e3d5a34)
- Revert "Set timeout even higher b/c sqlite just kills itself during these tests" [`f159252`](https://git.odit.services/lfk/backend/commit/f159252651942e442026dbcaae09b242e05d8204) - Revert "Set timeout even higher b/c sqlite just kills itself during these tests" [`f159252`](https://git.odit.services/lfk/backend/commit/f159252651942e442026dbcaae09b242e05d8204)
- Set timeout even higher b/c sqlite just kills itself during these tests [`6ab6099`](https://git.odit.services/lfk/backend/commit/6ab60998d4f716aded93bb3b5d15594fc5e0434a) - Set timeout even higher b/c sqlite just kills itself during these tests [`6ab6099`](https://git.odit.services/lfk/backend/commit/6ab60998d4f716aded93bb3b5d15594fc5e0434a)
- Adjusted jest timeout to mitigate sqlite from invalidateing all tests⏱ [`30d220b`](https://git.odit.services/lfk/backend/commit/30d220bc36a28f224406e49ed27ff3f6b4f409e9) - Adjusted jest timeout to mitigate sqlite from invalidateing all tests⏱ [`30d220b`](https://git.odit.services/lfk/backend/commit/30d220bc36a28f224406e49ed27ff3f6b4f409e9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`963253c`](https://git.odit.services/lfk/backend/commit/963253cbc84ed07af13ed0925952ec1b7dcc53ad) - 🧾New changelog file version [CI SKIP] [skip ci] [`963253c`](https://git.odit.services/lfk/backend/commit/963253cbc84ed07af13ed0925952ec1b7dcc53ad)
- 🧾New changelog file version [CI SKIP] [skip ci] [`3ef3a94`](https://git.odit.services/lfk/backend/commit/3ef3a94b20c1abf6fd2f19472e5f448b4c72bd7f)
- 🚀Bumped version to v0.7.1 [`135852e`](https://git.odit.services/lfk/backend/commit/135852eb9a91010a4ab972ba9efc7b71dfe4d68f)
- Merge pull request 'RESPONSERUNNERCARD fix bugfix/171-responserunnercards' (#172) from bugfix/171-responserunnercards into dev [`539a650`](https://git.odit.services/lfk/backend/commit/539a6509b17cfd373eef8e443eaa7d41168ac7a9)
- Now resolveing runnercards [`24aff3b`](https://git.odit.services/lfk/backend/commit/24aff3bac458a9886ca40163484bc72733dc766a) - Now resolveing runnercards [`24aff3b`](https://git.odit.services/lfk/backend/commit/24aff3bac458a9886ca40163484bc72733dc766a)
- Tests now keep the group [`f3d73d5`](https://git.odit.services/lfk/backend/commit/f3d73d53467a4d00011d280c24e1e12fbb8e443d) - Tests now keep the group [`f3d73d5`](https://git.odit.services/lfk/backend/commit/f3d73d53467a4d00011d280c24e1e12fbb8e443d)
- 🚀Bumped version to v0.7.1 [`135852e`](https://git.odit.services/lfk/backend/commit/135852eb9a91010a4ab972ba9efc7b71dfe4d68f)
- Merge pull request 'RESPONSERUNNERCARD fix bugfix/171-responserunnercards' (#172) from bugfix/171-responserunnercards into dev [`539a650`](https://git.odit.services/lfk/backend/commit/539a6509b17cfd373eef8e443eaa7d41168ac7a9)
- 🧾New changelog file version [CI SKIP] [skip ci] [`ce63043`](https://git.odit.services/lfk/backend/commit/ce63043887769e1f92a8c064d6647e0deb81b7fa) - 🧾New changelog file version [CI SKIP] [skip ci] [`ce63043`](https://git.odit.services/lfk/backend/commit/ce63043887769e1f92a8c064d6647e0deb81b7fa)
#### [v0.7.0](https://git.odit.services/lfk/backend/compare/v0.6.4...v0.7.0) #### [v0.7.0](https://git.odit.services/lfk/backend/compare/v0.6.4...v0.7.0)

View File

@ -1,23 +1,16 @@
# Typescript Build # Typescript Build
FROM registry.odit.services/hub/library/node:21.1.0-alpine3.18 as build FROM node:14.15.1-alpine3.12
ARG NPM_REGISTRY_URL=https://registry.npmjs.org
WORKDIR /app WORKDIR /app
COPY package.json ./ COPY package.json ./
RUN npm config set registry $NPM_REGISTRY_URL && npm i -g pnpm@8 RUN npm i -g pnpm
RUN mkdir /pnpm && pnpm config set store-dir /pnpm && pnpm i RUN pnpm i
COPY tsconfig.json ormconfig.js ./ COPY tsconfig.json ormconfig.js ./
COPY src ./src COPY src ./src
RUN pnpm run build \ RUN pnpm run build
&& rm -rf /app/node_modules \
&& pnpm i --production --prefer-offline
# final image # final image
FROM registry.odit.services/hub/library/node:21.1.0-alpine3.18 as final FROM node:14.15.1-alpine3.12
WORKDIR /app COPY package.json ormconfig.js ./
COPY --from=build /app/package.json /app/package.json RUN npm i -g pnpm
COPY --from=build /app/ormconfig.js /app/ormconfig.js RUN pnpm i --prod
COPY --from=build /app/dist /app/dist COPY --from=0 /app/dist dist
COPY --from=build /app/node_modules /app/node_modules ENTRYPOINT ["node", "dist/app.js"]
ENTRYPOINT ["node", "/app/dist/app.js"]

View File

@ -15,24 +15,24 @@ Backend Server
1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed) 1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed)
2. Install Dependencies 2. Install Dependencies
```bash ```bash
pnpm i yarn
``` ```
3. Start the server 3. Start the server
```bash ```bash
pnpm dev yarn dev
``` ```
### Run Tests ### Run Tests
```bash ```bash
# Run tests once (server has to run) # Run tests once (server has to run)
pnpm test yarn test
# Run test in watch mode (reruns on change) # Run test in watch mode (reruns on change)
pnpm test:watch yarn test:watch
# Run test in ci mode (automaticly starts the dev server) # Run test in ci mode (automaticly starts the dev server)
pnpm test:ci yarn test:ci
``` ```
### Use your own mail templates ### Use your own mail templates
@ -44,30 +44,30 @@ Currently the following templates exist:
### Generate Docs ### Generate Docs
```bash ```bash
pnpm docs yarn docs
``` ```
## ENV Vars ## ENV Vars
> You can provide them via .env file or docker env vars. > You can provide them via .env file or docker env vars.
> You can use the `test:ci:generate_env` package script to generate a example env (uses bs data as test server and ignores the errors). > You can use the `test:ci:generate_env` package script to generate a example env (uses bs data as test server and ignores the errors).
| Name | Type | Default | Description | | Name | Type | Default | Description
| ---------------------- | ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------- | | - | - | - | -
| APP_PORT | Number | 4010 | The port the backend server listens on. Is optional. | | APP_PORT | Number | 4010 | The port the backend server listens on. Is optional.
| DB_TYPE | String | N/A | The type of the db u want to use. It has to be supported by typeorm. Possible: `sqlite`, `mysql`, `postgresql` | | DB_TYPE | String | N/A | The type of the db u want to use. It has to be supported by typeorm. Possible: `sqlite`, `mysql`, `postgresql`
| DB_HOST | String | N/A | The db's host's ip-address/fqdn or file path for sqlite | | DB_HOST | String | N/A | The db's host's ip-address/fqdn or file path for sqlite
| DB_PORT | String | N/A | The db's port | | DB_PORT | String | N/A | The db's port
| DB_USER | String | N/A | The user for accessing the db | | DB_USER | String | N/A | The user for accessing the db
| DB_PASSWORD | String | N/A | The user's password for accessing the db | | DB_PASSWORD | String | N/A | The user's password for accessing the db
| DB_NAME | String | N/A | The db's name | | DB_NAME | String | N/A | The db's name
| NODE_ENV | String | dev | The apps env - influences debug info. Also when the env is set to "test", mailing errors get ignored. | | NODE_ENV | String | dev | The apps env - influences debug info. Also when the env is set to "test", mailing errors get ignored.
| POSTALCODE_COUNTRYCODE | String/CountryCode | N/A | The countrycode used to validate address's postal codes | | POSTALCODE_COUNTRYCODE | String/CountryCode | N/A | The countrycode used to validate address's postal codes
| PHONE_COUNTRYCODE | String/CountryCode | null (international) | The countrycode used to validate phone numers | | PHONE_COUNTRYCODE | String/CountryCode | null (international) | The countrycode used to validate phone numers
| SEED_TEST_DATA | Boolean | False | If you want the app to seed some example data set this to true | | SEED_TEST_DATA | Boolean | False | If you want the app to seed some example data set this to true
| MAILER_URL | String(Url) | N/A | The mailer's base url (no trailing slash) | | MAILER_URL | String(Url) | N/A | The mailer's base url (no trailing slash)
| MAILER_KEY | String | N/A | The mailer's api key. | | MAILER_KEY | String | N/A | The mailer's api key.
| IMPRINT_URL | String(Url) | /imprint | The link to a imprint page for the system (Defaults to the frontend's imprint) | | IMPRINT_URL | String(Url) | /imprint | The link to a imprint page for the system (Defaults to the frontend's imprint)
| PRIVACY_URL | String(Url) | /privacy | The link to a privacy page for the system (Defaults to the frontend's privacy page) | | PRIVACY_URL | String(Url) | /privacy | The link to a privacy page for the system (Defaults to the frontend's privacy page)
## Recommended Editor ## Recommended Editor
@ -85,10 +85,10 @@ pnpm docs
* A new release tag automaticly triggers the release ci pipeline * A new release tag automaticly triggers the release ci pipeline
* main: Protected "release" branch * main: Protected "release" branch
* The latest tag of the docker image get's build from this * The latest tag of the docker image get's build from this
* dev: Current dev branch for merging the different feature branches and bugfixes
* New releases get created as tags from this * New releases get created as tags from this
* dev: Current dev branch for merging the different feature branches and bugfixes
* The dev tag of the docker image get's build from this * The dev tag of the docker image get's build from this
* Only push minor changes to this branch! * Only push minor changes to this branch!
* To merge a feature branch into this please create a pull request * To merge a feature branch into this please create a pull request
* feature/xyz: Feature branches - naming scheme: `feature/issueid-title` * feature/xyz: Feature branches - nameing scheme: `feature/issueid-title`
* bugfix/xyz: Branches for bugfixes - naming scheme:`bugfix/issueid-title` * bugfix/xyz: Branches for bugfixes - nameing scheme:`bugfix/issueid-title`

View File

@ -11,12 +11,8 @@ services:
DB_PORT: bla DB_PORT: bla
DB_USER: bla DB_USER: bla
DB_PASSWORD: bla DB_PASSWORD: bla
DB_NAME: ./db.sqlite DB_NAME: dev.sqlite
NODE_ENV: production NODE_ENV: production
POSTALCODE_COUNTRYCODE: DE
SEED_TEST_DATA: "false"
MAILER_URL: https://dev.lauf-fuer-kaya.de/mailer
MAILER_KEY: asdasd
# APP_PORT: 4010 # APP_PORT: 4010
# DB_TYPE: postgres # DB_TYPE: postgres
# DB_HOST: backend_db # DB_HOST: backend_db

View File

@ -115,35 +115,6 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# check-password-strength
**Author**: deanilvincent
**Repo**: [object Object]
**License**: MIT
**Description**: A NPM Password strength checker based from Javascript RegExp. Check passphrase if it's "Weak", "Medium" or "Strong"
## License Text
MIT License
Copyright (c) 2020 Mark Deanil Vicente
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# class-transformer # class-transformer
**Author**: [object Object] **Author**: [object Object]
**Repo**: [object Object] **Repo**: [object Object]
@ -444,25 +415,6 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
**License**: MIT **License**: MIT
**Description**: A node.js driver for mysql. It is written in JavaScript, does not require compiling, and is 100% MIT licensed. **Description**: A node.js driver for mysql. It is written in JavaScript, does not require compiling, and is 100% MIT licensed.
## License Text ## License Text
Copyright (c) 2012 Felix Geisendörfer (felix@debuggable.com) and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# pg # pg
@ -715,75 +667,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# @faker-js/faker
**Author**: undefined
**Repo**: [object Object]
**License**: MIT
**Description**: Generate massive amounts of fake contextual data
## License Text
Faker - Copyright (c) 2022
This software consists of voluntary contributions made by many individuals.
For exact contribution history, see the revision history
available at https://github.com/faker-js/faker
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
===
From: https://github.com/faker-js/faker/commit/a9f98046c7d5eeaabe12fc587024c06d683800b8
To: https://github.com/faker-js/faker/commit/29234378807c4141588861f69421bf20b5ac635e
Based on faker.js, copyright Marak Squires and contributor, what follows below is the original license.
===
faker.js - Copyright (c) 2020
Marak Squires
http://github.com/marak/faker.js/
faker.js was inspired by and has used data definitions from:
* https://github.com/stympy/faker/ - Copyright (c) 2007-2010 Benjamin Curtis
* http://search.cpan.org/~jasonk/Data-Faker-0.07/ - Copyright 2004-2005 by Jason Kohles
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# @odit/license-exporter # @odit/license-exporter
**Author**: ODIT.Services **Author**: ODIT.Services
**Repo**: [object Object] **Repo**: [object Object]
@ -1014,35 +897,6 @@ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE SOFTWARE
# auto-changelog
**Author**: Pete Cook <pete@cookpete.com> (https://github.com/cookpete)
**Repo**: [object Object]
**License**: MIT
**Description**: Command line tool for generating a changelog from git tags and commit history
## License Text
The MIT License
Copyright (c) 2017 Pete Cook https://cookpete.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# cp-cli # cp-cli
**Author**: undefined **Author**: undefined
**Repo**: [object Object] **Repo**: [object Object]

View File

@ -1,11 +1,8 @@
{ {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"version": "1.1.3", "version": "0.7.1",
"main": "src/app.ts", "main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend", "repository": "https://git.odit.services/lfk/backend",
"engines": {
"pnpm": "8"
},
"author": { "author": {
"name": "ODIT.Services", "name": "ODIT.Services",
"email": "info@odit.services", "email": "info@odit.services",
@ -26,54 +23,51 @@
"license": "CC-BY-NC-SA-4.0", "license": "CC-BY-NC-SA-4.0",
"dependencies": { "dependencies": {
"@odit/class-validator-jsonschema": "2.1.1", "@odit/class-validator-jsonschema": "2.1.1",
"argon2": "0.27.1", "argon2": "^0.27.1",
"axios": "0.21.1", "axios": "^0.21.1",
"body-parser": "1.19.0", "body-parser": "^1.19.0",
"check-password-strength": "2.0.2",
"class-transformer": "0.3.1", "class-transformer": "0.3.1",
"class-validator": "0.13.1", "class-validator": "^0.13.1",
"consola": "2.15.0", "consola": "^2.15.0",
"cookie": "0.4.1", "cookie": "^0.4.1",
"cookie-parser": "1.4.5", "cookie-parser": "^1.4.5",
"cors": "2.8.5", "cors": "^2.8.5",
"csvtojson": "2.0.10", "csvtojson": "^2.0.10",
"dotenv": "8.2.0", "dotenv": "^8.2.0",
"express": "4.17.1", "express": "^4.17.1",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "^8.5.1",
"libphonenumber-js": "1.9.9", "libphonenumber-js": "^1.9.9",
"mysql": "2.18.1", "mysql": "^2.18.1",
"pg": "8.5.1", "pg": "^8.5.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "^0.1.13",
"routing-controllers": "0.9.0-alpha.6", "routing-controllers": "0.9.0-alpha.6",
"routing-controllers-openapi": "2.2.0", "routing-controllers-openapi": "^2.2.0",
"sqlite3": "5.0.0", "sqlite3": "5.0.0",
"typeorm": "0.2.30", "typeorm": "^0.2.30",
"typeorm-routing-controllers-extensions": "0.2.0", "typeorm-routing-controllers-extensions": "^0.2.0",
"typeorm-seeding": "1.6.1", "typeorm-seeding": "^1.6.1",
"uuid": "8.3.2", "uuid": "^8.3.2",
"validator": "13.5.2" "validator": "^13.5.2"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "7.6.0", "@odit/license-exporter": "^0.0.9",
"@odit/license-exporter": "0.0.9", "@types/cors": "^2.8.9",
"@types/cors": "2.8.9", "@types/csvtojson": "^1.1.5",
"@types/csvtojson": "1.1.5", "@types/express": "^4.17.11",
"@types/express": "4.17.11", "@types/jest": "^26.0.20",
"@types/jest": "26.0.20", "@types/jsonwebtoken": "^8.5.0",
"@types/jsonwebtoken": "8.5.0", "@types/node": "^14.14.22",
"@types/node": "14.14.22", "@types/uuid": "^8.3.0",
"@types/uuid": "8.3.0", "cp-cli": "^2.0.0",
"auto-changelog": "2.4.0", "jest": "^26.6.3",
"cp-cli": "2.0.0", "nodemon": "^2.0.7",
"jest": "26.6.3", "release-it": "^14.2.2",
"nodemon": "2.0.7", "rimraf": "^3.0.2",
"release-it": "14.2.2", "start-server-and-test": "^1.11.7",
"rimraf": "3.0.2", "ts-jest": "^26.5.0",
"start-server-and-test": "1.11.7", "ts-node": "^9.1.1",
"ts-jest": "26.5.0", "typedoc": "^0.20.19",
"ts-node": "9.1.1", "typescript": "^4.1.3"
"typedoc": "0.20.19",
"typescript": "4.1.3"
}, },
"scripts": { "scripts": {
"dev": "nodemon src/app.ts", "dev": "nodemon src/app.ts",
@ -87,7 +81,6 @@
"seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed", "seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
"openapi:export": "ts-node scripts/openapi_export.ts", "openapi:export": "ts-node scripts/openapi_export.ts",
"licenses:export": "license-exporter --markdown", "licenses:export": "license-exporter --markdown",
"changelog:export": "auto-changelog --commit-limit false -p -u --hide-credit",
"release": "release-it --only-version" "release": "release-it --only-version"
}, },
"release-it": { "release-it": {
@ -96,16 +89,11 @@
"requireCleanWorkingDir": false, "requireCleanWorkingDir": false,
"commitMessage": "🚀Bumped version to v${version}", "commitMessage": "🚀Bumped version to v${version}",
"requireBranch": "dev", "requireBranch": "dev",
"push": true, "push": false,
"tag": true, "tag": false
"tagName": "v${version}",
"tagAnnotation": "v${version}"
}, },
"npm": { "npm": {
"publish": false "publish": false
},
"hooks": {
"after:bump": "npm run changelog:export && npm run licenses:export && git add CHANGELOG.md && git add licenses.md"
} }
}, },
"nodemonConfig": { "nodemonConfig": {

7803
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors'; import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors';
import { DonorNotFoundError } from '../errors/DonorErrors'; import { DonorNotFoundError } from '../errors/DonorErrors';
import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors';
@ -36,16 +36,9 @@ export class DonationController {
@ResponseSchema(ResponseDonation, { isArray: true }) @ResponseSchema(ResponseDonation, { isArray: true })
@ResponseSchema(ResponseDistanceDonation, { 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).' }) @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(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseDonations: ResponseDonation[] = new Array<ResponseDonation>(); let responseDonations: ResponseDonation[] = new Array<ResponseDonation>();
let donations: Array<Donation>; const donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
if (page != undefined) {
donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'], skip: page * page_size, take: page_size });
} else {
donations = await this.donationRepository.find({ relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
}
donations.forEach(donation => { donations.forEach(donation => {
responseDonations.push(donation.toResponse()); responseDonations.push(donation.toResponse());
}); });

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors'; import { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
import { CreateDonor } from '../models/actions/create/CreateDonor'; import { CreateDonor } from '../models/actions/create/CreateDonor';
import { UpdateDonor } from '../models/actions/update/UpdateDonor'; import { UpdateDonor } from '../models/actions/update/UpdateDonor';
@ -25,16 +25,9 @@ export class DonorController {
@Authorized("DONOR:GET") @Authorized("DONOR:GET")
@ResponseSchema(ResponseDonor, { isArray: true }) @ResponseSchema(ResponseDonor, { isArray: true })
@OpenAPI({ description: 'Lists all donor. <br> This includes the donor\'s current donation amount.' }) @OpenAPI({ description: 'Lists all donor. <br> This includes the donor\'s current donation amount.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseDonors: ResponseDonor[] = new Array<ResponseDonor>(); let responseDonors: ResponseDonor[] = new Array<ResponseDonor>();
let donors: Array<Donor>; const donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
if (page != undefined) {
donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'], skip: page * page_size, take: page_size });
} else {
donors = await this.donorRepository.find({ relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
}
donors.forEach(donor => { donors.forEach(donor => {
responseDonors.push(new ResponseDonor(donor)); responseDonors.push(new ResponseDonor(donor));
}); });

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnection, getConnectionManager } from 'typeorm'; import { getConnection, getConnectionManager, Repository } from 'typeorm';
import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors'; import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { CreateGroupContact } from '../models/actions/create/CreateGroupContact'; import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
@ -26,16 +26,9 @@ export class GroupContactController {
@Authorized("CONTACT:GET") @Authorized("CONTACT:GET")
@ResponseSchema(ResponseGroupContact, { isArray: true }) @ResponseSchema(ResponseGroupContact, { isArray: true })
@OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' }) @OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>(); let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>();
let contacts: Array<GroupContact>; const contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] });
if (page != undefined) {
contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'], skip: page * page_size, take: page_size });
} else {
contacts = await this.contactRepository.find({ relations: ['groups', 'groups.parentGroup'] });
}
contacts.forEach(contact => { contacts.forEach(contact => {
responseContacts.push(contact.toResponse()); responseContacts.push(contact.toResponse());
}); });

View File

@ -1,7 +1,7 @@
import { Body, CurrentUser, Delete, Get, JsonController, OnUndefined, Put, QueryParam } from 'routing-controllers'; import { Body, CurrentUser, Delete, Get, JsonController, OnUndefined, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors'; import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UpdateUser } from '../models/actions/update/UpdateUser'; import { UpdateUser } from '../models/actions/update/UpdateUser';
import { User } from '../models/entities/User'; import { User } from '../models/entities/User';
import { ResponseUser } from '../models/responses/ResponseUser'; import { ResponseUser } from '../models/responses/ResponseUser';
@ -48,10 +48,6 @@ export class MeController {
@ResponseSchema(UserNotFoundError, { statusCode: 404 }) @ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 }) @ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 }) @ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainUppercaseLetterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainLowercaseLetterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainNumberError, { statusCode: 406 })
@ResponseSchema(PasswordTooShortError, { 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." }) @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) { async put(@CurrentUser() currentUser: User, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['groups'] }); let oldUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['groups'] });

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors'; import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors';
import { PrincipalNotFoundError } from '../errors/PrincipalErrors'; import { PrincipalNotFoundError } from '../errors/PrincipalErrors';
import { CreatePermission } from '../models/actions/create/CreatePermission'; import { CreatePermission } from '../models/actions/create/CreatePermission';
@ -27,16 +27,9 @@ export class PermissionController {
@Authorized("PERMISSION:GET") @Authorized("PERMISSION:GET")
@ResponseSchema(ResponsePermission, { isArray: true }) @ResponseSchema(ResponsePermission, { isArray: true })
@OpenAPI({ description: 'Lists all permissions for all users and groups.' }) @OpenAPI({ description: 'Lists all permissions for all users and groups.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responsePermissions: ResponsePermission[] = new Array<ResponsePermission>(); let responsePermissions: ResponsePermission[] = new Array<ResponsePermission>();
let permissions: Array<Permission>; const permissions = await this.permissionRepository.find({ relations: ['principal'] });
if (page != undefined) {
permissions = await this.permissionRepository.find({ relations: ['principal'], skip: page * page_size, take: page_size });
} else {
permissions = await this.permissionRepository.find({ relations: ['principal'] });
}
permissions.forEach(permission => { permissions.forEach(permission => {
responsePermissions.push(new ResponsePermission(permission)); responsePermissions.push(new ResponsePermission(permission));
}); });

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors'; import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors';
import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard'; import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard';
@ -26,16 +26,9 @@ export class RunnerCardController {
@Authorized("CARD:GET") @Authorized("CARD:GET")
@ResponseSchema(ResponseRunnerCard, { isArray: true }) @ResponseSchema(ResponseRunnerCard, { isArray: true })
@OpenAPI({ description: 'Lists all card.' }) @OpenAPI({ description: 'Lists all card.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>(); let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>();
let cards: Array<RunnerCard>; const cards = await this.cardRepository.find({ relations: ['runner', 'runner.group', 'runner.group.parentGroup'] });
if (page != undefined) {
cards = await this.cardRepository.find({ relations: ['runner', 'runner.group', 'runner.group.parentGroup'], skip: page * page_size, take: page_size });
} else {
cards = await this.cardRepository.find({ relations: ['runner', 'runner.group', 'runner.group.parentGroup'] });
}
cards.forEach(card => { cards.forEach(card => {
responseCards.push(new ResponseRunnerCard(card)); responseCards.push(new ResponseRunnerCard(card));
}); });
@ -57,23 +50,13 @@ export class RunnerCardController {
@Post('/bulk') @Post('/bulk')
@Authorized("CARD:CREATE") @Authorized("CARD:CREATE")
@ResponseSchema(ResponseEmpty, { statusCode: 200 }) @ResponseSchema(ResponseEmpty, { statusCode: 200 })
@OpenAPI({ description: "Create blank cards in bulk. <br> Just provide the count as a query param and wait for the 200 response. <br> You can provide the 'returnCards' query param if you want to receive the RESPONSERUNNERCARD objects in the response." }) @OpenAPI({ description: "Create blank cards in bulk. <br> Just provide the count as a query param and wait for the 200 response." })
async postBlancoBulk(@QueryParam("count") count: number, @QueryParam("returnCards") returnCards: boolean = false) { async postBlancoBulk(@QueryParam("count") count: number) {
let createPromises = new Array<any>(); let createPromises = new Array<any>();
for (let index = 0; index < count; index++) { for (let index = 0; index < count; index++) {
createPromises.push(this.cardRepository.save({ runner: null, enabled: true })) createPromises.push(this.cardRepository.save({ runner: null, enabled: true }))
} }
await Promise.all(createPromises);
const cards = await Promise.all(createPromises);
if (returnCards) {
let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>();
for await (let card of cards) {
let dbCard = await this.cardRepository.findOne({ id: card.id });
responseCards.push(new ResponseRunnerCard(dbCard));
}
return responseCards;
}
let response = new ResponseEmpty(); let response = new ResponseEmpty();
response.response = `Created ${count} new blanco cards.` response.response = `Created ${count} new blanco cards.`
return response; return response;

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors'; import { RunnerGroupNeededError, RunnerHasDistanceDonationsError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors'; import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
import { CreateRunner } from '../models/actions/create/CreateRunner'; import { CreateRunner } from '../models/actions/create/CreateRunner';
@ -30,16 +30,9 @@ export class RunnerController {
@Authorized("RUNNER:GET") @Authorized("RUNNER:GET")
@ResponseSchema(ResponseRunner, { isArray: true }) @ResponseSchema(ResponseRunner, { isArray: true })
@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' }) @OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>(); let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
let runners: Array<Runner>; const runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] });
if (page != undefined) {
runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track'], skip: page * page_size, take: page_size });
} else {
runners = await this.runnerRepository.find({ relations: ['scans', 'group', 'group.parentGroup', 'scans.track'] });
}
runners.forEach(runner => { runners.forEach(runner => {
responseRunners.push(new ResponseRunner(runner)); responseRunners.push(new ResponseRunner(runner));
}); });

View File

@ -1,6 +1,6 @@
import { Authorized, BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { RunnerOrganizationHasRunnersError, RunnerOrganizationHasTeamsError, RunnerOrganizationIdsNotMatchingError, RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors'; import { RunnerOrganizationHasRunnersError, RunnerOrganizationHasTeamsError, RunnerOrganizationIdsNotMatchingError, RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization'; import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization';
import { UpdateRunnerOrganization } from '../models/actions/update/UpdateRunnerOrganization'; import { UpdateRunnerOrganization } from '../models/actions/update/UpdateRunnerOrganization';
@ -29,20 +29,13 @@ export class RunnerOrganizationController {
@Authorized("ORGANIZATION:GET") @Authorized("ORGANIZATION:GET")
@ResponseSchema(ResponseRunnerOrganization, { isArray: true }) @ResponseSchema(ResponseRunnerOrganization, { isArray: true })
@OpenAPI({ description: 'Lists all organizations. <br> This includes their address, contact and teams (if existing/associated).' }) @OpenAPI({ description: 'Lists all organizations. <br> This includes their address, contact and teams (if existing/associated).' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseOrgs: ResponseRunnerOrganization[] = new Array<ResponseRunnerOrganization>(); let responseTeams: ResponseRunnerOrganization[] = new Array<ResponseRunnerOrganization>();
let orgs: Array<RunnerOrganization>; const runners = await this.runnerOrganizationRepository.find({ relations: ['contact', 'teams'] });
runners.forEach(runner => {
if (page != undefined) { responseTeams.push(new ResponseRunnerOrganization(runner));
orgs = await this.runnerOrganizationRepository.find({ relations: ['contact', 'teams'], skip: page * page_size, take: page_size });
} else {
orgs = await this.runnerOrganizationRepository.find({ relations: ['contact', 'teams'] });
}
orgs.forEach(org => {
responseOrgs.push(new ResponseRunnerOrganization(org));
}); });
return responseOrgs; return responseTeams;
} }
@Get('/:id') @Get('/:id')
@ -52,7 +45,7 @@ export class RunnerOrganizationController {
@OnUndefined(RunnerOrganizationNotFoundError) @OnUndefined(RunnerOrganizationNotFoundError)
@OpenAPI({ description: 'Lists all information about the organization whose id got provided.' }) @OpenAPI({ description: 'Lists all information about the organization whose id got provided.' })
async getOne(@Param('id') id: number) { async getOne(@Param('id') id: number) {
let runnerOrg = await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['contact', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.scans.track', 'runners', 'runners.scans', 'runners.scans.track'] }); let runnerOrg = await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['contact', 'teams'] });
if (!runnerOrg) { throw new RunnerOrganizationNotFoundError(); } if (!runnerOrg) { throw new RunnerOrganizationNotFoundError(); }
return new ResponseRunnerOrganization(runnerOrg); return new ResponseRunnerOrganization(runnerOrg);
} }
@ -121,10 +114,6 @@ export class RunnerOrganizationController {
@OnUndefined(204) @OnUndefined(204)
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organization still has runners and/or teams associated this will fail. <br> To delete the organization with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact. <br> If no organization with this id exists it will just return 204(no content).' }) @OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organization still has runners and/or teams associated this will fail. <br> To delete the organization with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact. <br> If no organization with this id exists it will just return 204(no content).' })
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) { async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
if (id == 1) {
throw new BadRequestError("You can't delete the citizen runner org.");
}
let organization = await this.runnerOrganizationRepository.findOne({ id: id }); let organization = await this.runnerOrganizationRepository.findOne({ id: id });
if (!organization) { return null; } if (!organization) { return null; }
let runnerOrganization = await this.runnerOrganizationRepository.findOne(organization, { relations: ['contact', 'runners', 'teams'] }); let runnerOrganization = await this.runnerOrganizationRepository.findOne(organization, { relations: ['contact', 'runners', 'teams'] });

View File

@ -1,12 +1,12 @@
import { Request } from "express"; import { Request } from "express";
import * as jwt from "jsonwebtoken"; import * as jwt from "jsonwebtoken";
import { BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; import { Body, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnectionManager, Repository } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { config } from '../config'; import { config } from '../config';
import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError'; import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError';
import { MailSendingError } from '../errors/MailErrors'; import { MailSendingError } from '../errors/MailErrors';
import { RunnerEmailNeededError, RunnerHasDistanceDonationsError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors'; import { RunnerEmailNeededError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors';
import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors'; import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { JwtCreator } from '../jwtcreator'; import { JwtCreator } from '../jwtcreator';
@ -23,9 +23,6 @@ import { ResponseScanStation } from '../models/responses/ResponseScanStation';
import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation'; import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation';
import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner'; import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan'; import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan';
import { DonationController } from './DonationController';
import { RunnerCardController } from './RunnerCardController';
import { ScanController } from './ScanController';
@JsonController() @JsonController()
export class RunnerSelfServiceController { export class RunnerSelfServiceController {
@ -46,50 +43,11 @@ export class RunnerSelfServiceController {
@ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError) @OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' }) @OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please contact support.' })
async get(@Param('jwt') token: string) { async get(@Param('jwt') token: string) {
return (new ResponseSelfServiceRunner(await this.getRunner(token))); return (new ResponseSelfServiceRunner(await this.getRunner(token)));
} }
@Delete('/runners/me/:jwt')
@ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(RunnerNotFoundError)
@OpenAPI({ description: 'Deletes all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' })
async remove(@Param('jwt') token: string, @QueryParam("force") force: boolean) {
const responseRunner = await this.getRunner(token);
let runner = await this.runnerRepository.findOne({ id: responseRunner.id });
if (!runner) { return null; }
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 ResponseSelfServiceRunner(responseRunner);
}
@Get('/runners/me/:jwt/scans') @Get('/runners/me/:jwt/scans')
@ResponseSchema(ResponseSelfServiceScan, { isArray: true }) @ResponseSchema(ResponseSelfServiceScan, { isArray: true })
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@ -116,22 +74,22 @@ export class RunnerSelfServiceController {
return scan.toResponse(); return scan.toResponse();
} }
@Post('/runners/login') @Post('/runners/forgot')
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
@OnUndefined(ResponseEmpty) @OnUndefined(ResponseEmpty)
@OpenAPI({ description: 'Use this endpoint to reuqest a new selfservice magic-login-link to be sent to your mail address (rate limited to one mail every 15mins).' }) @OpenAPI({ description: 'Use this endpoint to reuqest a new selfservice token/link to be sent to your mail address (rate limited to one mail every 24hrs).' })
async requestNewToken(@QueryParam('mail') mail: string, @QueryParam("locale") locale: string = "en") { async requestNewToken(@QueryParam('mail') mail: string) {
if (!mail) { if (!mail) {
throw new RunnerNotFoundError(); throw new RunnerNotFoundError();
} }
const runner = await this.runnerRepository.findOne({ email: mail }); const runner = await this.runnerRepository.findOne({ email: mail });
if (!runner) { throw new RunnerNotFoundError(); } if (!runner) { throw new RunnerNotFoundError(); }
if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 15)) { throw new RunnerSelfserviceTimeoutError(); } if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 60 * 24)) { throw new RunnerSelfserviceTimeoutError(); }
const token = JwtCreator.createSelfService(runner); const token = JwtCreator.createSelfService(runner);
try { try {
await Mailer.sendSelfserviceForgottenMail(runner.email, token, locale) await Mailer.sendSelfserviceForgottenMail(runner.email, token, "en")
} catch (error) { } catch (error) {
throw new MailSendingError(); throw new MailSendingError();
} }
@ -146,18 +104,15 @@ export class RunnerSelfServiceController {
@ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerEmailNeededError, { statusCode: 406 }) @ResponseSchema(RunnerEmailNeededError, { statusCode: 406 })
@OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.' }) @OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.' })
async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner, @QueryParam("locale") locale: string = "en") { async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner) {
let runner = await createRunner.toEntity(); let runner = await createRunner.toEntity();
if (await this.getRunnerExistsByMail(runner.email)) {
throw new BadRequestError("E-Mail already registered")
}
runner = await this.runnerRepository.save(runner);
runner = await this.runnerRepository.save(runner);
let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }));
response.token = JwtCreator.createSelfService(runner); response.token = JwtCreator.createSelfService(runner);
try { try {
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale) await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en")
} catch (error) { } catch (error) {
throw new MailSendingError(); throw new MailSendingError();
} }
@ -169,20 +124,17 @@ export class RunnerSelfServiceController {
@ResponseSchema(ResponseSelfServiceRunner) @ResponseSchema(ResponseSelfServiceRunner)
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 }) @ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
@OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.' }) @OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.' })
async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner, @QueryParam("locale") locale: string = "en") { async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner) {
const org = await this.getOrgansisation(token); const org = await this.getOrgansisation(token);
let runner = await createRunner.toEntity(org); let runner = await createRunner.toEntity(org);
if (await this.getRunnerExistsByMail(runner.email)) {
throw new BadRequestError("E-Mail already registered")
}
runner = await this.runnerRepository.save(runner); runner = await this.runnerRepository.save(runner);
let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] })); let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }));
response.token = JwtCreator.createSelfService(runner); response.token = JwtCreator.createSelfService(runner);
try { try {
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale) await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, "en")
} catch (error) { } catch (error) {
throw new MailSendingError(); throw new MailSendingError();
} }
@ -231,14 +183,4 @@ export class RunnerSelfServiceController {
return organization; return organization;
} }
/**
* Checks if a runner already exists
* @param email The runner's email address
* @returns Boolean (true if exists, false if not)
*/
private async getRunnerExistsByMail(email: string): Promise<boolean> {
const runner = await this.runnerRepository.findOne({ email });
return runner != undefined
}
} }

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors'; import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam'; import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam'; import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam';
@ -27,18 +27,11 @@ export class RunnerTeamController {
@Authorized("TEAM:GET") @Authorized("TEAM:GET")
@ResponseSchema(ResponseRunnerTeam, { isArray: true }) @ResponseSchema(ResponseRunnerTeam, { isArray: true })
@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organization and contact (if existing/associated).' }) @OpenAPI({ description: 'Lists all teams. <br> This includes their parent organization and contact (if existing/associated).' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>(); let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>();
let teams: Array<RunnerTeam>; const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
runners.forEach(runner => {
if (page != undefined) { responseTeams.push(new ResponseRunnerTeam(runner));
teams = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'], skip: page * page_size, take: page_size });
} else {
teams = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
}
teams.forEach(team => {
responseTeams.push(new ResponseRunnerTeam(team));
}); });
return responseTeams; return responseTeams;
} }
@ -50,7 +43,7 @@ export class RunnerTeamController {
@OnUndefined(RunnerTeamNotFoundError) @OnUndefined(RunnerTeamNotFoundError)
@OpenAPI({ description: 'Lists all information about the team whose id got provided.' }) @OpenAPI({ description: 'Lists all information about the team whose id got provided.' })
async getOne(@Param('id') id: number) { async getOne(@Param('id') id: number) {
let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact', 'runners', 'runners.scans', 'runners.scans.track'] }); let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
if (!runnerTeam) { throw new RunnerTeamNotFoundError(); } if (!runnerTeam) { throw new RunnerTeamNotFoundError(); }
return new ResponseRunnerTeam(runnerTeam); return new ResponseRunnerTeam(runnerTeam);
} }

View File

@ -1,7 +1,7 @@
import { Request } from "express"; import { Request } from "express";
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { RunnerNotFoundError } from '../errors/RunnerErrors'; import { RunnerNotFoundError } from '../errors/RunnerErrors';
import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors'; import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors';
import { ScanStationNotFoundError } from '../errors/ScanStationErrors'; import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
@ -34,16 +34,9 @@ export class ScanController {
@ResponseSchema(ResponseScan, { isArray: true }) @ResponseSchema(ResponseScan, { isArray: true })
@ResponseSchema(ResponseTrackScan, { 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.' }) @OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseScans: ResponseScan[] = new Array<ResponseScan>(); let responseScans: ResponseScan[] = new Array<ResponseScan>();
let scans: Array<Scan>; const scans = await this.scanRepository.find({ relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] });
if (page != undefined) {
scans = await this.scanRepository.find({ relations: ['runner', 'track'], skip: page * page_size, take: page_size });
} else {
scans = await this.scanRepository.find({ relations: ['runner', 'track'] });
}
scans.forEach(scan => { scans.forEach(scan => {
responseScans.push(scan.toResponse()); responseScans.push(scan.toResponse());
}); });
@ -58,7 +51,7 @@ export class ScanController {
@OnUndefined(ScanNotFoundError) @OnUndefined(ScanNotFoundError)
@OpenAPI({ description: 'Lists all information about the scan whose id got provided. This includes the scan\'s runner\'s distance ran.' }) @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) { async getOne(@Param('id') id: number) {
let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.group', 'card', 'station'] }) let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })
if (!scan) { throw new ScanNotFoundError(); } if (!scan) { throw new ScanNotFoundError(); }
return scan.toResponse(); return scan.toResponse();
} }

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors'; import { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors';
import { TrackNotFoundError } from '../errors/TrackErrors'; import { TrackNotFoundError } from '../errors/TrackErrors';
import { CreateScanStation } from '../models/actions/create/CreateScanStation'; import { CreateScanStation } from '../models/actions/create/CreateScanStation';
@ -26,16 +26,9 @@ export class ScanStationController {
@Authorized("STATION:GET") @Authorized("STATION:GET")
@ResponseSchema(ResponseScanStation, { isArray: true }) @ResponseSchema(ResponseScanStation, { isArray: true })
@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' }) @OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>(); let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>();
let stations: Array<ScanStation>; const stations = await this.stationRepository.find({ relations: ['track'] });
if (page != undefined) {
stations = await this.stationRepository.find({ relations: ['track'], skip: page * page_size, take: page_size });
} else {
stations = await this.stationRepository.find({ relations: ['track'] });
}
stations.forEach(station => { stations.forEach(station => {
responseStations.push(station.toResponse()); responseStations.push(station.toResponse());
}); });

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { StatsClientNotFoundError } from '../errors/StatsClientErrors'; import { StatsClientNotFoundError } from '../errors/StatsClientErrors';
import { TrackNotFoundError } from "../errors/TrackErrors"; import { TrackNotFoundError } from "../errors/TrackErrors";
import { CreateStatsClient } from '../models/actions/create/CreateStatsClient'; import { CreateStatsClient } from '../models/actions/create/CreateStatsClient';
@ -24,16 +24,9 @@ export class StatsClientController {
@Authorized("STATSCLIENT:GET") @Authorized("STATSCLIENT:GET")
@ResponseSchema(ResponseStatsClient, { isArray: true }) @ResponseSchema(ResponseStatsClient, { isArray: true })
@OpenAPI({ description: 'Lists all stats clients. Please remember that the key can only be viewed on creation.' }) @OpenAPI({ description: 'Lists all stats clients. Please remember that the key can only be viewed on creation.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseClients: ResponseStatsClient[] = new Array<ResponseStatsClient>(); let responseClients: ResponseStatsClient[] = new Array<ResponseStatsClient>();
let clients: Array<StatsClient>; const clients = await this.clientRepository.find();
if (page != undefined) {
clients = await this.clientRepository.find({ skip: page * page_size, take: page_size });
} else {
clients = await this.clientRepository.find();
}
clients.forEach(clients => { clients.forEach(clients => {
responseClients.push(new ResponseStatsClient(clients)); responseClients.push(new ResponseStatsClient(clients));
}); });

View File

@ -1,14 +1,12 @@
import { Get, JsonController, QueryParam, UseBefore } from 'routing-controllers'; import { Get, JsonController, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import StatsAuth from '../middlewares/StatsAuth'; import StatsAuth from '../middlewares/StatsAuth';
import { Donation } from '../models/entities/Donation'; import { Donation } from '../models/entities/Donation';
import { Donor } from '../models/entities/Donor';
import { Runner } from '../models/entities/Runner'; import { Runner } from '../models/entities/Runner';
import { RunnerOrganization } from '../models/entities/RunnerOrganization'; import { RunnerOrganization } from '../models/entities/RunnerOrganization';
import { RunnerTeam } from '../models/entities/RunnerTeam'; import { RunnerTeam } from '../models/entities/RunnerTeam';
import { Scan } from '../models/entities/Scan'; import { Scan } from '../models/entities/Scan';
import { TrackScan } from '../models/entities/TrackScan';
import { User } from '../models/entities/User'; import { User } from '../models/entities/User';
import { ResponseStats } from '../models/responses/ResponseStats'; import { ResponseStats } from '../models/responses/ResponseStats';
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization'; import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization';
@ -22,26 +20,14 @@ export class StatsController {
@ResponseSchema(ResponseStats) @ResponseSchema(ResponseStats)
@OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" }) @OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" })
async get() { async get() {
const connection = getConnection(); let connection = getConnection();
const runners = await connection.getRepository(Runner).count(); let runners = await connection.getRepository(Runner).find({ relations: ['scans', 'scans.track'] });
const teams = await connection.getRepository(RunnerTeam).count(); let teams = await connection.getRepository(RunnerTeam).find();
const orgs = await connection.getRepository(RunnerOrganization).count(); let orgs = await connection.getRepository(RunnerOrganization).find();
const users = await connection.getRepository(User).count(); let users = await connection.getRepository(User).find();
const scans = await connection.getRepository(Scan).count({ where: { valid: true } }); let scans = await connection.getRepository(Scan).find();
const distance_query = await connection.getRepository(Scan).createQueryBuilder('scan')
.leftJoinAndSelect("scan.track", "track").where("scan.valid = TRUE")
.select("SUM(track.distance)", "sum_track").addSelect("SUM(_distance)", "sum_distance")
.getRawOne();
let distace = parseInt(distance_query.sum_track)
if (distance_query.sum_distance) {
distace += parseInt(distance_query.sum_distance)
}
let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] }); let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
const donors = await connection.getRepository(Donor).count(); return new ResponseStats(runners, teams, orgs, users, scans, donations)
return new ResponseStats(runners, teams, orgs, users, scans, donations, distace, donors)
} }
@Get("/runners/distance") @Get("/runners/distance")
@ -50,10 +36,7 @@ export class StatsController {
@OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByDistance() { async getTopRunnersByDistance() {
let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] }); let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] });
if (!runners || runners.length == 0) { let topRunners = runners.sort((runner1, runner2) => runner1.distance - runner2.distance).slice(0, 9);
return [];
}
let topRunners = runners.sort((runner1, runner2) => runner2.distance - runner1.distance).slice(0, 10);
let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>(); let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>();
topRunners.forEach(runner => { topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner)); responseRunners.push(new ResponseStatsRunner(runner));
@ -66,11 +49,8 @@ export class StatsController {
@ResponseSchema(ResponseStatsRunner, { isArray: true }) @ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByDonations() { async getTopRunnersByDonations() {
let runners = await getConnection().getRepository(Runner).find({ relations: ['group', 'distanceDonations', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }); let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] });
if (!runners || runners.length == 0) { let topRunners = runners.sort((runner1, runner2) => runner1.distanceDonationAmount - runner2.distanceDonationAmount).slice(0, 9);
return [];
}
let topRunners = runners.sort((runner1, runner2) => runner2.distanceDonationAmount - runner1.distanceDonationAmount).slice(0, 10);
let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>(); let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>();
topRunners.forEach(runner => { topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner)); responseRunners.push(new ResponseStatsRunner(runner));
@ -78,34 +58,6 @@ export class StatsController {
return responseRunners; return responseRunners;
} }
@Get("/runners/laptime")
@UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by fastest laptime on your selected track (track by id).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByLaptime(@QueryParam("track") track: number) {
let scans = await getConnection().getRepository(TrackScan).find({ relations: ['track', 'runner', 'runner.group', 'runner.scans', 'runner.scans.track', 'runner.distanceDonations'] });
if (!scans || scans.length == 0) {
return [];
}
scans = scans.filter((s) => { return s.track.id == track && s.valid == true && s.lapTime != 0 }).sort((scan1, scan2) => scan1.lapTime - scan2.lapTime);
let topScans = new Array<TrackScan>();
let knownRunners = new Array<number>();
for (let i = 0; i < scans.length && topScans.length < 10; i++) {
const element = scans[i];
if (!knownRunners.includes(element.runner.id)) {
topScans.push(element);
knownRunners.push(element.runner.id);
}
}
let responseRunners: ResponseStatsRunner[] = new Array<ResponseStatsRunner>();
topScans.forEach(scan => {
responseRunners.push(new ResponseStatsRunner(scan.runner, scan.lapTime));
});
return responseRunners;
}
@Get("/scans") @Get("/scans")
@UseBefore(StatsAuth) @UseBefore(StatsAuth)
@ResponseSchema(ResponseStatsRunner, { isArray: true }) @ResponseSchema(ResponseStatsRunner, { isArray: true })
@ -119,11 +71,8 @@ export class StatsController {
@ResponseSchema(ResponseStatsTeam, { isArray: true }) @ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopTeamsByDistance() { async getTopTeamsByDistance() {
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.scans.track'] }); let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] });
if (!teams || teams.length == 0) { let topTeams = teams.sort((team1, team2) => team1.distance - team2.distance).slice(0, 9);
return [];
}
let topTeams = teams.sort((team1, team2) => team2.distance - team1.distance).slice(0, 10);
let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>(); let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>();
topTeams.forEach(team => { topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team)); responseTeams.push(new ResponseStatsTeam(team));
@ -136,11 +85,8 @@ export class StatsController {
@ResponseSchema(ResponseStatsTeam, { isArray: true }) @ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopTeamsByDonations() { async getTopTeamsByDonations() {
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] }); let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] });
if (!teams || teams.length == 0) { let topTeams = teams.sort((team1, team2) => team1.distanceDonationAmount - team2.distanceDonationAmount).slice(0, 9);
return [];
}
let topTeams = teams.sort((team1, team2) => team2.distanceDonationAmount - team1.distanceDonationAmount).slice(0, 10);
let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>(); let responseTeams: ResponseStatsTeam[] = new Array<ResponseStatsTeam>();
topTeams.forEach(team => { topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team)); responseTeams.push(new ResponseStatsTeam(team));
@ -154,10 +100,7 @@ export class StatsController {
@OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopOrgsByDistance() { async getTopOrgsByDistance() {
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] }); let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
if (!orgs || orgs.length == 0) { let topOrgs = orgs.sort((org1, org2) => org1.distance - org2.distance).slice(0, 9);
return [];
}
let topOrgs = orgs.sort((org1, org2) => org2.distance - org1.distance).slice(0, 10);
let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>(); let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
topOrgs.forEach(org => { topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org)); responseOrgs.push(new ResponseStatsOrgnisation(org));
@ -170,11 +113,8 @@ export class StatsController {
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true }) @ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopOrgsByDonations() { async getTopOrgsByDonations() {
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.distanceDonations', 'runners.distanceDonations.runner', 'runners.distanceDonations.runner.scans', 'runners.distanceDonations.runner.scans.track', 'teams', 'teams.runners', 'teams.runners.distanceDonations', 'teams.runners.distanceDonations.runner', 'teams.runners.distanceDonations.runner.scans', 'teams.runners.distanceDonations.runner.scans.track'] }); let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
if (!orgs || orgs.length == 0) { let topOrgs = orgs.sort((org1, org2) => org1.distanceDonationAmount - org2.distanceDonationAmount).slice(0, 9);
return [];
}
let topOrgs = orgs.sort((org1, org2) => org2.distanceDonationAmount - org1.distanceDonationAmount).slice(0, 10);
let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>(); let responseOrgs: ResponseStatsOrgnisation[] = new Array<ResponseStatsOrgnisation>();
topOrgs.forEach(org => { topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org)); responseOrgs.push(new ResponseStatsOrgnisation(org));

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors"; import { TrackHasScanStationsError, TrackIdsNotMatchingError, TrackLapTimeCantBeNegativeError, TrackNotFoundError } from "../errors/TrackErrors";
import { CreateTrack } from '../models/actions/create/CreateTrack'; import { CreateTrack } from '../models/actions/create/CreateTrack';
import { UpdateTrack } from '../models/actions/update/UpdateTrack'; import { UpdateTrack } from '../models/actions/update/UpdateTrack';
@ -25,17 +25,9 @@ export class TrackController {
@Authorized("TRACK:GET") @Authorized("TRACK:GET")
@ResponseSchema(ResponseTrack, { isArray: true }) @ResponseSchema(ResponseTrack, { isArray: true })
@OpenAPI({ description: 'Lists all tracks.' }) @OpenAPI({ description: 'Lists all tracks.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseTracks: ResponseTrack[] = new Array<ResponseTrack>(); let responseTracks: ResponseTrack[] = new Array<ResponseTrack>();
let tracks: Array<Track>; const tracks = await this.trackRepository.find();
if (page != undefined) {
tracks = await this.trackRepository.find({ skip: page * page_size, take: page_size });
}
else {
tracks = await this.trackRepository.find();
}
tracks.forEach(track => { tracks.forEach(track => {
responseTracks.push(new ResponseTrack(track)); responseTracks.push(new ResponseTrack(track));
}); });

View File

@ -1,7 +1,7 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserDeletionNotConfirmedError, UserIdsNotMatchingError, UserNotFoundError, UsernameContainsIllegalCharacterError } from '../errors/UserErrors'; import { UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
import { UserGroupNotFoundError } from '../errors/UserGroupErrors'; import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUser } from '../models/actions/create/CreateUser'; import { CreateUser } from '../models/actions/create/CreateUser';
import { UpdateUser } from '../models/actions/update/UpdateUser'; import { UpdateUser } from '../models/actions/update/UpdateUser';
@ -28,17 +28,9 @@ export class UserController {
@Authorized("USER:GET") @Authorized("USER:GET")
@ResponseSchema(ResponseUser, { isArray: true }) @ResponseSchema(ResponseUser, { isArray: true })
@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions granted to them.' }) @OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions granted to them.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseUsers: ResponseUser[] = new Array<ResponseUser>(); let responseUsers: ResponseUser[] = new Array<ResponseUser>();
let users: Array<User>; const users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] });
if (page != undefined) {
users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'], skip: page * page_size, take: page_size });
}
else {
users = await this.userRepository.find({ relations: ['permissions', 'groups', 'groups.permissions'] });
}
users.forEach(user => { users.forEach(user => {
responseUsers.push(new ResponseUser(user)); responseUsers.push(new ResponseUser(user));
}); });
@ -74,10 +66,6 @@ export class UserController {
@ResponseSchema(ResponseUser) @ResponseSchema(ResponseUser)
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 }) @ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 }) @ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainUppercaseLetterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainLowercaseLetterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainNumberError, { statusCode: 406 })
@ResponseSchema(PasswordTooShortError, { 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.' }) @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) { async post(@Body({ validate: true }) createUser: CreateUser) {
let user; let user;
@ -97,10 +85,6 @@ export class UserController {
@ResponseSchema(UserNotFoundError, { statusCode: 404 }) @ResponseSchema(UserNotFoundError, { statusCode: 404 })
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 }) @ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 }) @ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainUppercaseLetterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainLowercaseLetterError, { statusCode: 406 })
@ResponseSchema(PasswordMustContainNumberError, { statusCode: 406 })
@ResponseSchema(PasswordTooShortError, { 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." }) @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) { async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) {
let oldUser = await this.userRepository.findOne({ id: id }); let oldUser = await this.userRepository.findOne({ id: id });

View File

@ -1,6 +1,6 @@
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers'; import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnectionManager } from 'typeorm'; import { getConnectionManager, Repository } from 'typeorm';
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors'; import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
import { CreateUserGroup } from '../models/actions/create/CreateUserGroup'; import { CreateUserGroup } from '../models/actions/create/CreateUserGroup';
import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup'; import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup';
@ -27,16 +27,9 @@ export class UserGroupController {
@Authorized("USERGROUP:GET") @Authorized("USERGROUP:GET")
@ResponseSchema(ResponseUserGroup, { isArray: true }) @ResponseSchema(ResponseUserGroup, { isArray: true })
@OpenAPI({ description: 'Lists all groups. <br> The information provided might change while the project continues to evolve.' }) @OpenAPI({ description: 'Lists all groups. <br> The information provided might change while the project continues to evolve.' })
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) { async getAll() {
let responseGroups: ResponseUserGroup[] = new Array<ResponseUserGroup>(); let responseGroups: ResponseUserGroup[] = new Array<ResponseUserGroup>();
let groups: Array<UserGroup>; const groups = await this.userGroupsRepository.find({ relations: ['permissions'] });
if (page != undefined) {
groups = await this.userGroupsRepository.find({ relations: ['permissions'], skip: page * page_size, take: page_size });
} else {
groups = await this.userGroupsRepository.find({ relations: ['permissions'] });
}
groups.forEach(group => { groups.forEach(group => {
responseGroups.push(group.toResponse()); responseGroups.push(group.toResponse());
}); });

View File

@ -72,32 +72,3 @@ export class UserDeletionNotConfirmedError extends NotAcceptableError {
@IsString() @IsString()
message = "You are trying to delete a user! \n If you're sure about doing this: provide the ?force=true query param." message = "You are trying to delete a user! \n If you're sure about doing this: provide the ?force=true query param."
} }
export class PasswordMustContainUppercaseLetterError extends NotAcceptableError {
@IsString()
name = "PasswordMustContainUppercaseLetterError"
@IsString()
message = "Passwords must contain at least one uppercase letter."
}
export class PasswordMustContainLowercaseLetterError extends NotAcceptableError {
@IsString()
name = "PasswordMustContainLowercaseLetterError"
@IsString()
message = "Passwords must contain at least one lowercase letter."
}
export class PasswordMustContainNumberError extends NotAcceptableError {
@IsString()
name = "PasswordMustContainNumberError"
@IsString()
message = "Passwords must contain at least one number."
}
export class PasswordTooShortError extends NotAcceptableError {
@IsString()
name = "PasswordTooShortError"
@IsString()
message = "Passwords must be at least ten characters long."
}

View File

@ -33,7 +33,6 @@ export class CreateDistanceDonation extends CreateDonation {
let newDonation = new DistanceDonation; let newDonation = new DistanceDonation;
newDonation.amountPerDistance = this.amountPerDistance; newDonation.amountPerDistance = this.amountPerDistance;
newDonation.paidAmount = this.paidAmount;
newDonation.donor = await this.getDonor(); newDonation.donor = await this.getDonor();
newDonation.runner = await this.getRunner(); newDonation.runner = await this.getRunner();

View File

@ -1,4 +1,4 @@
import { IsInt, IsOptional, IsPositive } from 'class-validator'; import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors'; import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation'; import { Donation } from '../../entities/Donation';
@ -16,13 +16,6 @@ export abstract class CreateDonation {
@IsPositive() @IsPositive()
donor: number; donor: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
@IsOptional()
paidAmount?: number;
/** /**
* Creates a new Donation entity from this. * Creates a new Donation entity from this.
*/ */

View File

@ -21,7 +21,6 @@ export class CreateFixedDonation extends CreateDonation {
let newDonation = new FixedDonation; let newDonation = new FixedDonation;
newDonation.amount = this.amount; newDonation.amount = this.amount;
newDonation.paidAmount = this.paidAmount;
newDonation.donor = await this.getDonor(); newDonation.donor = await this.getDonor();
return newDonation; return newDonation;

View File

@ -57,12 +57,11 @@ export class CreateTrackScan {
* @returns The runnerCard whom's id you provided. * @returns The runnerCard whom's id you provided.
*/ */
public async getCard(): Promise<RunnerCard> { public async getCard(): Promise<RunnerCard> {
const id = this.card % 200000000000; const track = await getConnection().getRepository(RunnerCard).findOne({ id: this.card }, { relations: ["runner"] });
const runnerCard = await getConnection().getRepository(RunnerCard).findOne({ id: id }, { relations: ["runner"] }); if (!track) {
if (!runnerCard) {
throw new RunnerCardNotFoundError(); throw new RunnerCardNotFoundError();
} }
return runnerCard; return track;
} }
/** /**
@ -86,13 +85,14 @@ export class CreateTrackScan {
* @returns The validated scan with it's laptime set. * @returns The validated scan with it's laptime set.
*/ */
public async validateScan(scan: TrackScan): Promise<TrackScan> { public async validateScan(scan: TrackScan): Promise<TrackScan> {
const latestScan = await getConnection().getRepository(TrackScan).findOne({ where: { runner: scan.runner, valid: true }, relations: ["track"], order: { id: 'DESC' } }); const scans = await getConnection().getRepository(TrackScan).find({ where: { runner: scan.runner, valid: true }, relations: ["track"] });
if (!latestScan) { if (scans.length == 0) {
scan.lapTime = 0; scan.lapTime = 0;
scan.valid = true; scan.valid = true;
} }
else { else {
scan.lapTime = scan.timestamp - latestScan.timestamp; const newestScan = scans[scans.length - 1];
scan.lapTime = scan.timestamp - newestScan.timestamp;
scan.valid = (scan.lapTime > scan.track.minimumLapTime); scan.valid = (scan.lapTime > scan.track.minimumLapTime);
} }
return scan; return scan;

View File

@ -1,10 +1,9 @@
import * as argon2 from "argon2"; import * as argon2 from "argon2";
import { passwordStrength } from "check-password-strength";
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator'; import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid'; import * as uuid from 'uuid';
import { config } from '../../../config'; import { config } from '../../../config';
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors'; import { UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors'; import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
import { User } from '../../entities/User'; import { User } from '../../entities/User';
import { UserGroup } from '../../entities/UserGroup'; import { UserGroup } from '../../entities/UserGroup';
@ -95,13 +94,7 @@ export class CreateUser {
if (!this.email) { if (!this.email) {
throw new UserEmailNeededError(); throw new UserEmailNeededError();
} }
if (this.username?.includes("@")) { throw new UsernameContainsIllegalCharacterError(); } if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
let password_strength = passwordStrength(this.password);
if (!password_strength.contains.includes("uppercase")) { throw new PasswordMustContainUppercaseLetterError(); }
if (!password_strength.contains.includes("lowercase")) { throw new PasswordMustContainLowercaseLetterError(); }
if (!password_strength.contains.includes("number")) { throw new PasswordMustContainNumberError(); }
if (!(password_strength.length > 9)) { throw new PasswordTooShortError(); }
newUser.email = this.email newUser.email = this.email
newUser.username = this.username newUser.username = this.username
@ -114,7 +107,7 @@ export class CreateUser {
newUser.groups = await this.getGroups(); newUser.groups = await this.getGroups();
newUser.enabled = this.enabled; newUser.enabled = this.enabled;
if (!this.profilePic) { newUser.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; } if (!this.profilePic) { newUser.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
else { newUser.profilePic = this.profilePic; } else { newUser.profilePic = this.profilePic; }
return newUser; return newUser;

View File

@ -32,7 +32,6 @@ export class UpdateDistanceDonation extends UpdateDonation {
*/ */
public async update(donation: DistanceDonation): Promise<DistanceDonation> { public async update(donation: DistanceDonation): Promise<DistanceDonation> {
donation.amountPerDistance = this.amountPerDistance; donation.amountPerDistance = this.amountPerDistance;
donation.paidAmount = this.paidAmount;
donation.donor = await this.getDonor(); donation.donor = await this.getDonor();
donation.runner = await this.getRunner(); donation.runner = await this.getRunner();

View File

@ -1,4 +1,4 @@
import { IsInt, IsOptional, IsPositive } from 'class-validator'; import { IsInt, IsPositive } from 'class-validator';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { DonorNotFoundError } from '../../../errors/DonorErrors'; import { DonorNotFoundError } from '../../../errors/DonorErrors';
import { Donation } from '../../entities/Donation'; import { Donation } from '../../entities/Donation';
@ -23,13 +23,6 @@ export abstract class UpdateDonation {
@IsPositive() @IsPositive()
donor: number; donor: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
@IsOptional()
paidAmount?: number;
/** /**
* Creates a new Donation entity from this. * Creates a new Donation entity from this.
*/ */

View File

@ -20,7 +20,6 @@ export class UpdateFixedDonation extends UpdateDonation {
*/ */
public async update(donation: FixedDonation): Promise<FixedDonation> { public async update(donation: FixedDonation): Promise<FixedDonation> {
donation.amount = this.amount; donation.amount = this.amount;
donation.paidAmount = this.paidAmount;
donation.donor = await this.getDonor(); donation.donor = await this.getDonor();
return donation; return donation;

View File

@ -1,14 +1,12 @@
import * as argon2 from "argon2"; import * as argon2 from "argon2";
import { passwordStrength } from "check-password-strength";
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator'; import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
import { config } from '../../../config'; import { config } from '../../../config';
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors'; import { UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors'; import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
import { User } from '../../entities/User'; import { User } from '../../entities/User';
import { UserGroup } from '../../entities/UserGroup'; import { UserGroup } from '../../entities/UserGroup';
/** /**
* This class is used to update a User entity (via put request). * This class is used to update a User entity (via put request).
*/ */
@ -106,11 +104,6 @@ export class UpdateUser {
if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); } if (this.username.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
if (this.password) { if (this.password) {
let password_strength = passwordStrength(this.password);
if (!password_strength.contains.includes("uppercase")) { throw new PasswordMustContainUppercaseLetterError(); }
if (!password_strength.contains.includes("lowercase")) { throw new PasswordMustContainLowercaseLetterError(); }
if (!password_strength.contains.includes("number")) { throw new PasswordMustContainNumberError(); }
if (!(password_strength.length > 9)) { throw new PasswordTooShortError(); }
user.password = await argon2.hash(this.password + user.uuid); user.password = await argon2.hash(this.password + user.uuid);
user.refreshTokenCount = user.refreshTokenCount + 1; user.refreshTokenCount = user.refreshTokenCount + 1;
} }
@ -124,7 +117,7 @@ export class UpdateUser {
user.phone = this.phone; user.phone = this.phone;
user.groups = await this.getGroups(); user.groups = await this.getGroups();
if (!this.profilePic) { user.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; } if (!this.profilePic) { user.profilePic = `https://dev.lauf-fuer-kaya.de/lfk-logo.png`; }
else { user.profilePic = this.profilePic; } else { user.profilePic = this.profilePic; }
return user; return user;

View File

@ -2,7 +2,7 @@ import {
IsInt, IsInt,
IsNotEmpty IsNotEmpty
} from "class-validator"; } from "class-validator";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseDonation } from '../responses/ResponseDonation'; import { ResponseDonation } from '../responses/ResponseDonation';
import { Donor } from './Donor'; import { Donor } from './Donor';
@ -34,13 +34,6 @@ export abstract class Donation {
*/ */
public abstract get amount(): number; public abstract get amount(): number;
/**
* The donation's paid amount in cents (or whatever your currency's smallest unit is.).
* Used to mark donations as paid.
*/
@Column({ nullable: true })
@IsInt()
paidAmount: number;
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.

View File

@ -33,15 +33,6 @@ export class Donor extends Participant {
return this.donations.reduce((sum, current) => sum + current.amount, 0); return this.donations.reduce((sum, current) => sum + current.amount, 0);
} }
/**
* Returns the total paid donations of a donor based on his linked donations.
*/
@IsInt()
public get paidDonationAmount(): number {
if (!this.donations) { return 0; }
return this.donations.reduce((sum, current) => sum + current.paidAmount, 0);
}
/** /**
* Turns this entity into it's response class. * Turns this entity into it's response class.
*/ */

View File

@ -52,7 +52,13 @@ export class RunnerCard {
* Generates a ean-13 compliant string for barcode generation. * Generates a ean-13 compliant string for barcode generation.
*/ */
public get code(): string { public get code(): string {
return this.paddedId const multiply = [1, 3];
let total = 0;
this.paddedId.split('').forEach((letter, index) => {
total += parseInt(letter, 10) * multiply[index % 2];
});
const checkSum = (Math.ceil(total / 10) * 10) - total;
return this.paddedId + checkSum.toString();
} }
/** /**
@ -61,11 +67,10 @@ export class RunnerCard {
private get paddedId(): string { private get paddedId(): string {
let id: string = this.id.toString(); let id: string = this.id.toString();
if (id.length > 11) { if (id.length > 12) {
throw new RunnerCardIdOutOfRangeError(); throw new RunnerCardIdOutOfRangeError();
} }
while (id.length < 11) { id = '0' + id; } while (id.length < 12) { id = '0' + id; }
id = '2' + id;
return id; return id;
} }

View File

@ -51,9 +51,6 @@ export abstract class RunnerGroup {
*/ */
@IsInt() @IsInt()
public get distance(): number { public get distance(): number {
if (!this.runners || this.runners.length == 0) {
return 0;
}
return this.runners.reduce((sum, current) => sum + current.distance, 0); return this.runners.reduce((sum, current) => sum + current.distance, 0);
} }

View File

@ -1,7 +0,0 @@
/**
* This enum contains all status a donation can inherit regarding it's payment status.
*/
export enum DonationStatus {
OPEN = 'OPEN',
PAID = 'PAID'
}

View File

@ -35,5 +35,4 @@ export enum ResponseObjectType {
USER = 'USER', USER = 'USER',
USERGROUP = 'USERGROUP', USERGROUP = 'USERGROUP',
USERPERMISSIONS = 'USERPERMISSIONS', USERPERMISSIONS = 'USERPERMISSIONS',
SELFSERVICEDONOR = 'SELFSERVICEDONOR'
} }

View File

@ -1,6 +1,5 @@
import { IsInt, IsNotEmpty, IsPositive } from "class-validator"; import { IsInt, IsNotEmpty, IsPositive } from "class-validator";
import { Donation } from '../entities/Donation'; import { Donation } from '../entities/Donation';
import { DonationStatus } from '../enums/DonationStatus';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseDonor } from './ResponseDonor'; import { ResponseDonor } from './ResponseDonor';
@ -16,12 +15,6 @@ export class ResponseDonation implements IResponse {
*/ */
responseType: ResponseObjectType = ResponseObjectType.DONATION; responseType: ResponseObjectType = ResponseObjectType.DONATION;
/**
* The donation's payment status.
* Provides you with a quick indicator of it's payment status.
*/
status: DonationStatus;
/** /**
* The donation's id. * The donation's id.
*/ */
@ -41,28 +34,13 @@ export class ResponseDonation implements IResponse {
@IsInt() @IsInt()
amount: number; amount: number;
/**
* The donation's paid amount in the smalles unit of your currency (default: euro cent).
*/
@IsInt()
paidAmount: number;
/** /**
* Creates a ResponseDonation object from a scan. * Creates a ResponseDonation object from a scan.
* @param donation The donation the response shall be build for. * @param donation The donation the response shall be build for.
*/ */
public constructor(donation: Donation) { public constructor(donation: Donation) {
this.id = donation.id; this.id = donation.id;
if (donation.donor) { this.donor = donation.donor.toResponse();
this.donor = donation.donor.toResponse();
}
this.amount = donation.amount; this.amount = donation.amount;
this.paidAmount = donation.paidAmount || 0;
if (this.paidAmount < this.amount) {
this.status = DonationStatus.OPEN;
}
else {
this.status = DonationStatus.PAID;
}
} }
} }

View File

@ -4,7 +4,6 @@ import {
import { Donor } from '../entities/Donor'; import { Donor } from '../entities/Donor';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseDonation } from './ResponseDonation';
import { ResponseParticipant } from './ResponseParticipant'; import { ResponseParticipant } from './ResponseParticipant';
/** /**
@ -29,14 +28,6 @@ export class ResponseDonor extends ResponseParticipant implements IResponse {
@IsInt() @IsInt()
donationAmount: number; donationAmount: number;
/**
* Returns the total paid donations of a donor based on his linked donations.
*/
@IsInt()
paidDonationAmount: number;
donations: Array<ResponseDonation>;
/** /**
* Creates a ResponseRunner object from a runner. * Creates a ResponseRunner object from a runner.
* @param runner The user the response shall be build for. * @param runner The user the response shall be build for.
@ -45,12 +36,5 @@ export class ResponseDonor extends ResponseParticipant implements IResponse {
super(donor); super(donor);
this.receiptNeeded = donor.receiptNeeded; this.receiptNeeded = donor.receiptNeeded;
this.donationAmount = donor.donationAmount; this.donationAmount = donor.donationAmount;
this.paidDonationAmount = donor.paidDonationAmount;
this.donations = new Array<ResponseDonation>();
if (donor.donations?.length > 0) {
for (const donation of donor.donations) {
this.donations.push(donation.toResponse())
}
}
} }
} }

View File

@ -1,4 +1,4 @@
import { IsInt, IsNotEmpty, IsNumber, IsObject, IsOptional, IsString } from "class-validator"; import { IsInt, IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator";
import { RunnerGroup } from '../entities/RunnerGroup'; import { RunnerGroup } from '../entities/RunnerGroup';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
@ -36,10 +36,6 @@ export abstract class ResponseRunnerGroup implements IResponse {
@IsOptional() @IsOptional()
contact?: ResponseGroupContact; contact?: ResponseGroupContact;
@IsOptional()
@IsNumber()
total_distance: number
/** /**
* Creates a ResponseRunnerGroup object from a runnerGroup. * Creates a ResponseRunnerGroup object from a runnerGroup.
* @param group The runnerGroup the response shall be build for. * @param group The runnerGroup the response shall be build for.
@ -48,6 +44,5 @@ export abstract class ResponseRunnerGroup implements IResponse {
this.id = group.id; this.id = group.id;
this.name = group.name; this.name = group.name;
if (group.contact) { this.contact = group.contact.toResponse(); }; if (group.contact) { this.contact = group.contact.toResponse(); };
if (group.runners) { this.total_distance = group.runners.reduce((p, c) => p + c.distance, 0) }
} }
} }

View File

@ -67,9 +67,6 @@ export class ResponseRunnerOrganization extends ResponseRunnerGroup implements I
for (let team of org.teams) { for (let team of org.teams) {
this.teams.push(team.toResponse()); this.teams.push(team.toResponse());
} }
for (const team of this.teams) {
this.total_distance += team.total_distance;
}
} }
if (!org.key) { this.registrationEnabled = false; } if (!org.key) { this.registrationEnabled = false; }

View File

@ -2,7 +2,6 @@ import { IsInt, IsNotEmpty, IsPositive } from 'class-validator';
import { DistanceDonation } from '../entities/DistanceDonation'; import { DistanceDonation } from '../entities/DistanceDonation';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseSelfServiceDonor } from './ResponseSelfServiceDonor';
/** /**
* Defines the runner selfservice donation response. * Defines the runner selfservice donation response.
@ -19,7 +18,7 @@ export class ResponseSelfServiceDonation implements IResponse {
* The donation's donor. * The donation's donor.
*/ */
@IsNotEmpty() @IsNotEmpty()
donor: ResponseSelfServiceDonor; donor: string;
/** /**
* The donation's amount in the smalles unit of your currency (default: euro cent). * The donation's amount in the smalles unit of your currency (default: euro cent).
@ -36,7 +35,9 @@ export class ResponseSelfServiceDonation implements IResponse {
amountPerDistance: number; amountPerDistance: number;
public constructor(donation: DistanceDonation) { public constructor(donation: DistanceDonation) {
this.donor = new ResponseSelfServiceDonor(donation.donor); if (!donation.donor.middlename) { this.donor = donation.donor.firstname + " " + donation.donor.lastname; }
else { this.donor = donation.donor.firstname + " " + donation.donor.middlename + " " + donation.donor.lastname; }
this.amountPerDistance = donation.amountPerDistance; this.amountPerDistance = donation.amountPerDistance;
this.amount = donation.amount; this.amount = donation.amount;
} }

View File

@ -1,51 +0,0 @@
import { IsInt, IsString } from "class-validator";
import { Donor } from '../entities/Donor';
import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse';
/**
* Defines the donor selfservice response.
* Why? B/C runner's are not allowed to view all information available to admin users.
*/
export class ResponseSelfServiceDonor implements IResponse {
/**
* The responseType.
* This contains the type of class/entity this response contains.
*/
responseType: ResponseObjectType = ResponseObjectType.SELFSERVICEDONOR;
/**
* The participant's id.
*/
@IsInt()
id: number;
/**
* The participant's first name.
*/
@IsString()
firstname: string;
/**
* The participant's middle name.
*/
@IsString()
middlename?: string;
/**
* The participant's last name.
*/
@IsString()
lastname: string;
/**
* Creates a ResponseSelfServiceDonor object from a runner.
* @param donor The donor the response shall be build for.
*/
public constructor(donor: Donor) {
this.id = donor.id;
this.firstname = donor.firstname;
this.middlename = donor.middlename;
this.lastname = donor.lastname;
}
}

View File

@ -38,10 +38,10 @@ export class ResponseSelfServiceRunner extends ResponseParticipant implements IR
group: string; group: string;
/** /**
* The runner's associated distance donations. * The runner's associated donations.
*/ */
@IsString() @IsString()
distanceDonations: ResponseSelfServiceDonation[] donations: ResponseSelfServiceDonation[]
/** /**
* The runner's self-service jwt for auth. * The runner's self-service jwt for auth.
@ -60,7 +60,7 @@ export class ResponseSelfServiceRunner extends ResponseParticipant implements IR
this.distance = runner.distance; this.distance = runner.distance;
this.donationAmount = runner.distanceDonationAmount; this.donationAmount = runner.distanceDonationAmount;
this.group = this.getTeamString(runner.group); this.group = this.getTeamString(runner.group);
this.distanceDonations = this.getDonations(runner.distanceDonations); this.donations = this.getDonations(runner.distanceDonations);
} }
/** /**

View File

@ -2,6 +2,11 @@ import {
IsInt IsInt
} from "class-validator"; } from "class-validator";
import { Donation } from '../entities/Donation'; import { Donation } from '../entities/Donation';
import { Runner } from '../entities/Runner';
import { RunnerOrganization } from '../entities/RunnerOrganization';
import { RunnerTeam } from '../entities/RunnerTeam';
import { Scan } from '../entities/Scan';
import { User } from '../entities/User';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
@ -58,30 +63,12 @@ export class ResponseStats implements IResponse {
@IsInt() @IsInt()
total_donation: number; total_donation: number;
/**
* The total donation count (cent).
*/
@IsInt()
total_donations: number;
/**
* The total donor count.
*/
@IsInt()
total_donors: number;
/** /**
* The average distance ran per runner. * The average distance ran per runner.
*/ */
@IsInt() @IsInt()
average_distance: number; average_distance: number;
/**
* The average donation per distance (cent).
*/
@IsInt()
average_donation: number;
/** /**
* Creates a new stats response containing some basic statistics for a dashboard or public display. * Creates a new stats response containing some basic statistics for a dashboard or public display.
* @param runners Array containing all runners - the following relations have to be resolved: scans, scans.track * @param runners Array containing all runners - the following relations have to be resolved: scans, scans.track
@ -91,17 +78,14 @@ export class ResponseStats implements IResponse {
* @param scans Array containing all scans - no relations have to be resolved. * @param scans Array containing all scans - no relations have to be resolved.
* @param donations Array containing all donations - the following relations have to be resolved: runner, runner.scans, runner.scans.track * @param donations Array containing all donations - the following relations have to be resolved: runner, runner.scans, runner.scans.track
*/ */
public constructor(runners: number, teams: number, orgs: number, users: number, scans: number, donations: Donation[], distance: number, donors: number) { public constructor(runners: Runner[], teams: RunnerTeam[], orgs: RunnerOrganization[], users: User[], scans: Scan[], donations: Donation[]) {
this.total_runners = runners; this.total_runners = runners.length;
this.total_teams = teams; this.total_teams = teams.length;
this.total_orgs = orgs; this.total_orgs = orgs.length;
this.total_users = users; this.total_users = users.length;
this.total_scans = scans; this.total_scans = scans.filter(scan => { scan.valid === true }).length;
this.total_distance = distance; this.total_distance = runners.reduce((sum, current) => sum + current.distance, 0);
this.total_donation = donations.reduce((sum, current) => sum + current.amount, 0); this.total_donation = donations.reduce((sum, current) => sum + current.amount, 0);
this.total_donations = donations.length;
this.average_donation = this.total_donation / this.total_donations
this.total_donors = donors;
this.average_distance = this.total_distance / this.total_runners; this.average_distance = this.total_distance / this.total_runners;
} }
} }

View File

@ -49,15 +49,7 @@ export class ResponseStatsOrgnisation implements IResponse {
public constructor(org: RunnerOrganization) { public constructor(org: RunnerOrganization) {
this.name = org.name; this.name = org.name;
this.id = org.id; this.id = org.id;
try { this.distance = org.distance;
this.distance = org.distance; this.donationAmount = org.distanceDonationAmount;
} catch {
this.distance = -1;
}
try {
this.donationAmount = org.distanceDonationAmount;
} catch {
this.donationAmount = -1;
}
} }
} }

View File

@ -1,7 +1,6 @@
import { import {
IsInt, IsInt,
IsObject, IsObject,
IsOptional,
IsString IsString
} from "class-validator"; } from "class-validator";
import { Runner } from '../entities/Runner'; import { Runner } from '../entities/Runner';
@ -56,13 +55,6 @@ export class ResponseStatsRunner implements IResponse {
@IsInt() @IsInt()
donationAmount: number; donationAmount: number;
/**
* The runner's fastest laptime in seconds.
*/
@IsInt()
@IsOptional()
minLaptime?: number;
/** /**
* The runner's group. * The runner's group.
*/ */
@ -73,28 +65,13 @@ export class ResponseStatsRunner implements IResponse {
* Creates a new runner stats response from a runner * Creates a new runner stats response from a runner
* @param runner The runner whoes response shall be generated - the following relations have to be resolved: scans, group, distanceDonations, scans.track * @param runner The runner whoes response shall be generated - the following relations have to be resolved: scans, group, distanceDonations, scans.track
*/ */
public constructor(runner: Runner, laptime?: number) { public constructor(runner: Runner) {
this.id = runner.id; this.id = runner.id;
this.firstname = runner.firstname; this.firstname = runner.firstname;
if (runner.firstname) { this.middlename = runner.middlename;
this.middlename = runner.middlename;
}
this.lastname = runner.lastname; this.lastname = runner.lastname;
try { this.distance = runner.distance;
this.distance = runner.distance; this.donationAmount = runner.distanceDonationAmount;
}
catch {
this.distance = -1;
}
try {
this.donationAmount = runner.distanceDonationAmount;
}
catch {
this.donationAmount = -1;
}
if (laptime) {
this.minLaptime = laptime;
}
this.group = runner.group.toResponse(); this.group = runner.group.toResponse();
} }
} }

View File

@ -57,15 +57,7 @@ export class ResponseStatsTeam implements IResponse {
this.name = team.name; this.name = team.name;
this.id = team.id; this.id = team.id;
this.parent = team.parentGroup.toResponse(); this.parent = team.parentGroup.toResponse();
try { this.distance = team.distance;
this.distance = team.distance; this.donationAmount = team.distanceDonationAmount;
} catch {
this.distance = -1;
}
try {
this.donationAmount = team.distanceDonationAmount;
} catch {
this.donationAmount = -1;
}
} }
} }

View File

@ -1,22 +1,22 @@
import * as argon2 from "argon2";
import { Connection } from 'typeorm'; import { Connection } from 'typeorm';
import { Factory, Seeder } from 'typeorm-seeding'; import { Factory, Seeder } from 'typeorm-seeding';
import * as uuid from 'uuid';
import { CreatePermission } from '../models/actions/create/CreatePermission'; import { CreatePermission } from '../models/actions/create/CreatePermission';
import { CreateUser } from '../models/actions/create/CreateUser';
import { CreateUserGroup } from '../models/actions/create/CreateUserGroup'; import { CreateUserGroup } from '../models/actions/create/CreateUserGroup';
import { Permission } from '../models/entities/Permission'; import { Permission } from '../models/entities/Permission';
import { User } from '../models/entities/User'; import { User } from '../models/entities/User';
import { UserGroup } from '../models/entities/UserGroup'; import { UserGroup } from '../models/entities/UserGroup';
import { PermissionAction } from '../models/enums/PermissionAction'; import { PermissionAction } from '../models/enums/PermissionAction';
import { PermissionTarget } from '../models/enums/PermissionTargets'; import { PermissionTarget } from '../models/enums/PermissionTargets';
/** /**
* Seeds a admin group with a demo user into the database for initial setup and auto recovery. * Seeds a admin group with a demo user into the database for initial setup and auto recovery.
* We know that the naming isn't perfectly fitting. Feel free to change it. * We know that the nameing isn't perfectly fitting. Feel free to change it.
*/ */
export default class SeedUsers implements Seeder { export default class SeedUsers implements Seeder {
public async run(factory: Factory, connection: Connection): Promise<any> { public async run(factory: Factory, connection: Connection): Promise<any> {
let adminGroup: UserGroup = await this.createAdminGroup(connection); let adminGroup: UserGroup = await this.createAdminGroup(connection);
await this.createUser(connection, adminGroup); await this.createUser(connection, adminGroup.id);
await this.createPermissions(connection, adminGroup.id); await this.createPermissions(connection, adminGroup.id);
} }
@ -27,16 +27,15 @@ export default class SeedUsers implements Seeder {
return await connection.getRepository(UserGroup).save(await adminGroup.toEntity()); return await connection.getRepository(UserGroup).save(await adminGroup.toEntity());
} }
public async createUser(connection: Connection, group: UserGroup) { public async createUser(connection: Connection, group: number) {
let initialUser = new User(); let initialUser = new CreateUser();
initialUser.firstname = "demo"; initialUser.firstname = "demo";
initialUser.lastname = "demo"; initialUser.lastname = "demo";
initialUser.username = "demo"; initialUser.username = "demo";
initialUser.uuid = uuid.v4(); initialUser.password = "demo";
initialUser.password = await argon2.hash("demo" + initialUser.uuid);
initialUser.email = "demo@dev.lauf-fuer-kaya.de" initialUser.email = "demo@dev.lauf-fuer-kaya.de"
initialUser.groups = [group]; initialUser.groups = group;
return await connection.getRepository(User).save(initialUser); return await connection.getRepository(User).save(await initialUser.toEntity());
} }
public async createPermissions(connection: Connection, principal: number) { public async createPermissions(connection: Connection, principal: number) {

View File

@ -11,12 +11,12 @@ beforeAll(async () => {
jest.setTimeout(20000); jest.setTimeout(20000);
const res_login = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); const res_login = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
await axios.post(base + '/api/users', { await axios.post(base + '/api/users', {
"firstname": "demo_logoutASD123", "firstname": "demo_logout",
"middlename": "demo_logoutASD123", "middlename": "demo_logout",
"lastname": "demo_logoutASD123", "lastname": "demo_logout",
"username": "demo_logoutASD123", "username": "demo_logout",
"password": "demo_logoutASD123", "password": "demo_logout",
"email": "demo_logoutASD123@dev.lauf-fuer-kaya.de" "email": "demo_logout@dev.lauf-fuer-kaya.de"
}, { }, {
headers: { "authorization": "Bearer " + res_login.data["access_token"] }, headers: { "authorization": "Bearer " + res_login.data["access_token"] },
validateStatus: undefined validateStatus: undefined
@ -26,7 +26,7 @@ beforeAll(async () => {
describe('POST /api/auth/logout valid', () => { describe('POST /api/auth/logout valid', () => {
let refresh_coookie; let refresh_coookie;
it('valid logout with token in cookie should return 200', async () => { it('valid logout with token in cookie should return 200', async () => {
const res_login = await axios.post(base + '/api/auth/login', { username: "demo_logoutASD123", password: "demo_logoutASD123" }); const res_login = await axios.post(base + '/api/auth/login', { username: "demo_logout", password: "demo_logout" });
refresh_coookie = res_login.headers["set-cookie"]; refresh_coookie = res_login.headers["set-cookie"];
const res = await axios.post(base + '/api/auth/logout', null, { const res = await axios.post(base + '/api/auth/logout', null, {
headers: { "Cookie": refresh_coookie }, headers: { "Cookie": refresh_coookie },
@ -35,7 +35,7 @@ describe('POST /api/auth/logout valid', () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
}); });
it('valid logout with token in body should return 200', async () => { it('valid logout with token in body should return 200', async () => {
const res_login = await axios.post(base + '/api/auth/login', { username: "demo_logoutASD123", password: "demo_logoutASD123" }); const res_login = await axios.post(base + '/api/auth/login', { username: "demo_logout", password: "demo_logout" });
const res = await axios.post(base + '/api/auth/logout', { token: res_login.data["refresh_token"] }, axios_config); const res = await axios.post(base + '/api/auth/logout', { token: res_login.data["refresh_token"] }, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
}); });

View File

@ -11,12 +11,12 @@ beforeAll(async () => {
jest.setTimeout(20000); jest.setTimeout(20000);
const res_login = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); const res_login = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
await axios.post(base + '/api/users', { await axios.post(base + '/api/users', {
"firstname": "demo_refreshASD312", "firstname": "demo_refresh",
"middlename": "demo_refreshASD312", "middlename": "demo_refresh",
"lastname": "demo_refreshASD312", "lastname": "demo_refresh",
"username": "demo_refreshASD312", "username": "demo_refresh",
"password": "demo_refreshASD312", "password": "demo_refresh",
"email": "demo_refreshASD312@dev.lauf-fuer-kaya.de" "email": "demo_refresh@dev.lauf-fuer-kaya.de"
}, { }, {
headers: { "authorization": "Bearer " + res_login.data["access_token"] }, headers: { "authorization": "Bearer " + res_login.data["access_token"] },
validateStatus: undefined validateStatus: undefined
@ -25,7 +25,7 @@ beforeAll(async () => {
describe('POST /api/auth/refresh valid', () => { describe('POST /api/auth/refresh valid', () => {
it('valid refresh with token in cookie should return 200', async () => { it('valid refresh with token in cookie should return 200', async () => {
const res_login = await axios.post(base + '/api/auth/login', { username: "demo_refreshASD312", password: "demo_refreshASD312" }); const res_login = await axios.post(base + '/api/auth/login', { username: "demo_refresh", password: "demo_refresh" });
const res = await axios.post(base + '/api/auth/refresh', null, { const res = await axios.post(base + '/api/auth/refresh', null, {
headers: { "Cookie": res_login.headers["set-cookie"] }, headers: { "Cookie": res_login.headers["set-cookie"] },
validateStatus: undefined validateStatus: undefined
@ -33,7 +33,7 @@ describe('POST /api/auth/refresh valid', () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
}); });
it('valid refresh with token in body should return 200', async () => { it('valid refresh with token in body should return 200', async () => {
const res_login = await axios.post(base + '/api/auth/login', { username: "demo_refreshASD312", password: "demo_refreshASD312" }); const res_login = await axios.post(base + '/api/auth/login', { username: "demo_refresh", password: "demo_refresh" });
const res = await axios.post(base + '/api/auth/refresh', { token: res_login.data["refresh_token"] }, axios_config); const res = await axios.post(base + '/api/auth/refresh', { token: res_login.data["refresh_token"] }, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
}); });

View File

@ -11,23 +11,23 @@ beforeAll(async () => {
jest.setTimeout(20000); jest.setTimeout(20000);
const res_login = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" }); const res_login = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
await axios.post(base + '/api/users', { await axios.post(base + '/api/users', {
"firstname": "demo_resetASD312", "firstname": "demo_reset",
"middlename": "demo_resetASD312", "middlename": "demo_reset",
"lastname": "demo_resetASD312", "lastname": "demo_reset",
"username": "demo_resetASD312", "username": "demo_reset",
"password": "demo_resetASD312", "password": "demo_reset",
"email": "demo_resetASD3121@dev.lauf-fuer-kaya.de" "email": "demo_reset1@dev.lauf-fuer-kaya.de"
}, { }, {
headers: { "authorization": "Bearer " + res_login.data["access_token"] }, headers: { "authorization": "Bearer " + res_login.data["access_token"] },
validateStatus: undefined validateStatus: undefined
}); });
await axios.post(base + '/api/users', { await axios.post(base + '/api/users', {
"firstname": "demo_resetASD3122", "firstname": "demo_reset2",
"middlename": "demo_resetASD3122", "middlename": "demo_reset2",
"lastname": "demo_resetASD3122", "lastname": "demo_reset2",
"username": "demo_resetASD3122", "username": "demo_reset2",
"password": "demo_resetASD3122", "password": "demo_reset2",
"email": "demo_resetASD3122@dev.lauf-fuer-kaya.de" "email": "demo_reset2@dev.lauf-fuer-kaya.de"
}, { }, {
headers: { "authorization": "Bearer " + res_login.data["access_token"] }, headers: { "authorization": "Bearer " + res_login.data["access_token"] },
validateStatus: undefined validateStatus: undefined
@ -37,7 +37,7 @@ beforeAll(async () => {
describe('POST /api/auth/reset valid', () => { describe('POST /api/auth/reset valid', () => {
let reset_token; let reset_token;
it('valid reset token request should return 200 (500 w/o correct auth)', async () => { it('valid reset token request should return 200 (500 w/o correct auth)', async () => {
const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_resetASD3121@dev.lauf-fuer-kaya.de" }, axios_config); const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset1@dev.lauf-fuer-kaya.de" }, axios_config);
reset_token = res1.data.resetToken; reset_token = res1.data.resetToken;
expect(res1.status).toEqual(200); expect(res1.status).toEqual(200);
}); });
@ -45,8 +45,8 @@ describe('POST /api/auth/reset valid', () => {
// --------------- // ---------------
describe('POST /api/auth/reset invalid requests', () => { describe('POST /api/auth/reset invalid requests', () => {
it('request another password reset before the timeout should return 406', async () => { it('request another password reset before the timeout should return 406', async () => {
const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_resetASD3122@dev.lauf-fuer-kaya.de" }, axios_config); const res1 = await axios.post(base + '/api/auth/reset', { email: "demo_reset2@dev.lauf-fuer-kaya.de" }, axios_config);
const res2 = await axios.post(base + '/api/auth/reset', { email: "demo_resetASD3122@dev.lauf-fuer-kaya.de" }, axios_config); const res2 = await axios.post(base + '/api/auth/reset', { email: "demo_reset2@dev.lauf-fuer-kaya.de" }, axios_config);
expect(res2.status).toEqual(406); expect(res2.status).toEqual(406);
}); });
}); });

View File

@ -156,23 +156,11 @@ describe('POST /api/cards/bulk successfully', () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
}); });
it('creating a single new bulk card and letting the system return it should return 200', async () => {
const res = await axios.post(base + '/api/cards/bulk?count=1&returnCards=true', {}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data[0].id).toBeDefined();
});
it('creating 50 new bulk card should return 200', async () => { it('creating 50 new bulk card should return 200', async () => {
const res = await axios.post(base + '/api/cards/bulk?count=50', {}, axios_config); const res = await axios.post(base + '/api/cards/bulk?count=50', {}, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
}); });
it('creating 50 new bulk cards and letting the system return it should return 200', async () => {
const res = await axios.post(base + '/api/cards/bulk?count=50&returnCards=true', {}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data.length).toEqual(50);
});
it('creating 250 new bulk card should return 200', async () => { it('creating 250 new bulk card should return 200', async () => {
const res = await axios.post(base + '/api/cards/bulk?count=250', {}, axios_config); const res = await axios.post(base + '/api/cards/bulk?count=250', {}, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);

View File

@ -1,19 +0,0 @@
import axios from 'axios';
async function main() {
console.time("batches")
for (let i = 0; i < 100; i++) {
const batch = [];
for (let i = 0; i < 6; i++) {
batch.push(axios.post('http://localhost:4010/api/scans/trackscans', { card: 200000000001, station: 2 }, {
headers: {
Authorization: 'Bearer 10F2E64.BB4F6CC5-2148-4CCF-88B5-0AA85D0508A9'
}
}))
}
await Promise.all(batch)
console.timeLog("batches", `Finished batch ${i}`)
}
console.timeEnd("batches")
}
main();

View File

@ -170,7 +170,7 @@ describe('POST /api/donations/fixed successfully', () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
}); });
it('creating a new fixed donation with more params should return 200', async () => { it('creating a new fixed donation should return 200', async () => {
const res = await axios.post(base + '/api/donations/fixed', { const res = await axios.post(base + '/api/donations/fixed', {
"donor": added_donor.id, "donor": added_donor.id,
"amount": 1000 "amount": 1000
@ -181,25 +181,6 @@ describe('POST /api/donations/fixed successfully', () => {
expect(res.data).toEqual({ expect(res.data).toEqual({
"donor": added_donor, "donor": added_donor,
"amount": 1000, "amount": 1000,
"paidAmount": 0,
"status": "OPEN",
"responseType": "DONATION"
});
});
it('creating a new fixed donation with all params should return 200', async () => {
const res = await axios.post(base + '/api/donations/fixed', {
"donor": added_donor.id,
"amount": 1000,
"paidAmount": 1000
}, axios_config);
delete res.data.id;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"donor": added_donor,
"amount": 1000,
"paidAmount": 1000,
"status": "PAID",
"responseType": "DONATION" "responseType": "DONATION"
}); });
}); });
@ -238,7 +219,7 @@ describe('POST /api/donations/distance successfully', () => {
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json") expect(res.headers['content-type']).toContain("application/json")
}); });
it('creating a new distance donation with most params should return 200', async () => { it('creating a new fixed donation should return 200', async () => {
const res = await axios.post(base + '/api/donations/distance', { const res = await axios.post(base + '/api/donations/distance', {
"runner": added_runner.id, "runner": added_runner.id,
"amountPerDistance": 100, "amountPerDistance": 100,
@ -252,28 +233,6 @@ describe('POST /api/donations/distance successfully', () => {
"amountPerDistance": 100, "amountPerDistance": 100,
"runner": added_runner, "runner": added_runner,
"amount": 0, "amount": 0,
"paidAmount": 0,
"status": "PAID",
"responseType": "DISTANCEDONATION"
})
});
it('creating a new distance donation with all params should return 200', async () => {
const res = await axios.post(base + '/api/donations/distance', {
"runner": added_runner.id,
"amountPerDistance": 100,
"donor": added_donor.id,
"paidAmount": 1000
}, axios_config);
delete res.data.id;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data).toEqual({
"donor": added_donor,
"amountPerDistance": 100,
"runner": added_runner,
"amount": 0,
"paidAmount": 1000,
"status": "PAID",
"responseType": "DISTANCEDONATION" "responseType": "DISTANCEDONATION"
}) })
}); });

View File

@ -213,17 +213,6 @@ describe('adding + updating fixed donation valid', () => {
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
expect(res.data.amount).toEqual(42); expect(res.data.amount).toEqual(42);
}); });
it('updating paidAmount should return 200', async () => {
const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, {
"id": added_donation.id,
"donor": added_donor.id,
"amount": 42,
"paidAmount": 10
}, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data.paidAmount).toEqual(10);
});
it('updating donor should return 200', async () => { it('updating donor should return 200', async () => {
const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, { const res = await axios.put(base + '/api/donations/fixed/' + added_donation.id, {
"id": added_donation.id, "id": added_donation.id,
@ -328,19 +317,6 @@ describe('adding + updating distance donation valid', () => {
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
expect(res.data.amountPerDistance).toEqual(69); expect(res.data.amountPerDistance).toEqual(69);
}); });
it('updating paidAmount should return 200', async () => {
const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, {
"id": added_donation.id,
"runner": added_runner.id,
"amountPerDistance": 69,
"donor": added_donor.id,
"paidAmount": 10
}, axios_config);
delete res.data.donor.donationAmount;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
expect(res.data.paidAmount).toEqual(10);
});
it('updating runner should return 200', async () => { it('updating runner should return 200', async () => {
const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, { const res = await axios.put(base + '/api/donations/distance/' + added_donation.id, {
"id": added_donation.id, "id": added_donation.id,

View File

@ -16,18 +16,12 @@ beforeAll(async () => {
}); });
// --------------- // ---------------
describe('deletion (non-existant)', () => { describe('adding + deletion (non-existant)', () => {
it('delete', async () => { it('delete', async () => {
const res2 = await axios.delete(base + '/api/organizations/0', axios_config); const res2 = await axios.delete(base + '/api/organizations/0', axios_config);
expect(res2.status).toEqual(204); expect(res2.status).toEqual(204);
}); });
}); });
describe('deletion of citizen sould fail', () => {
it('delete', async () => {
const res3 = await axios.delete(base + '/api/organizations/1', axios_config);
expect(res3.status).toEqual(400);
});
});
// --------------- // ---------------
describe('adding + deletion (successfull)', () => { describe('adding + deletion (successfull)', () => {
let added_org_id let added_org_id

View File

@ -66,8 +66,6 @@ describe('adding + getting scans', () => {
const res = await axios.get(base + '/api/scans/' + added_scan.id, axios_config); const res = await axios.get(base + '/api/scans/' + added_scan.id, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
delete res.data.runner.distance;
delete added_scan.runner.distance;
expect(res.data).toEqual(added_scan); expect(res.data).toEqual(added_scan);
}); });
it('check if scans was added via the runner/scans endpoint.', async () => { it('check if scans was added via the runner/scans endpoint.', async () => {

View File

@ -1,68 +0,0 @@
import { faker } from '@faker-js/faker';
import axios from 'axios';
import { config } from '../../config';
const base = "http://localhost:" + config.internal_port
let access_token;
let axios_config;
beforeAll(async () => {
jest.setTimeout(20000);
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
access_token = res.data["access_token"];
axios_config = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined
};
});
// ---------------
describe('delete selfservice runner invalid', () => {
let added_runner;
it('registering as citizen with minimal params should return 200', async () => {
const res = await axios.post(base + '/api/runners/register', {
"firstname": "string",
"lastname": "string",
"email": faker.internet.exampleEmail(),
}, axios_config);
added_runner = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('delete with valid jwt should return 200', async () => {
const res = await axios.delete(base + '/api/runners/me/' + added_runner.token, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('delete with valid jwt but non-existant runner should return 200', async () => {
const res = await axios.delete(base + '/api/runners/me/' + added_runner.token, axios_config);
expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json");
});
it('delete with invalid jwt should return 401', async () => {
const res = await axios.delete(base + '/api/runners/me/123.123', axios_config);
expect(res.status).toEqual(401);
expect(res.headers['content-type']).toContain("application/json");
});
});
// ---------------
describe('delete selfservice runner valid', () => {
let added_runner;
it('registering as citizen with minimal params should return 200', async () => {
const res = await axios.post(base + '/api/runners/register', {
"firstname": "string",
"lastname": "string",
"email": faker.internet.exampleEmail(),
}, axios_config);
added_runner = res.data;
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('delete with valid jwt should return 200', async () => {
const res = await axios.delete(base + '/api/runners/me/' + added_runner.token, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
delete added_runner.token;
expect(res.data).toEqual(added_runner);
});
});

View File

@ -15,20 +15,20 @@ beforeAll(async () => {
}; };
}); });
describe('POST /api/runners/me/login invalid syntax/mail should fail', () => { describe('POST /api/runners/me/forgot invalid syntax/mail should fail', () => {
it('get without mail return 404', async () => { it('get without mail return 404', async () => {
const res = await axios.post(base + '/api/runners/login', null, axios_config); const res = await axios.post(base + '/api/runners/forgot', null, axios_config);
expect(res.status).toEqual(404); expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
}); });
it('get without bs mail return 404', async () => { it('get without bs mail return 404', async () => {
const res = await axios.post(base + '/api/runners/login?mail=asdasdasdasdasd@tester.test.dev.lauf-fuer-kaya.de', null, axios_config); const res = await axios.post(base + '/api/runners/forgot?mail=asdasdasdasdasd@tester.test.dev.lauf-fuer-kaya.de', null, axios_config);
expect(res.status).toEqual(404); expect(res.status).toEqual(404);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
}); });
}); });
// --------------- // ---------------
describe('POST /api/runners/me/login 2 times within timeout should fail', () => { describe('POST /api/runners/me/forgot 2 times within timeout should fail', () => {
let added_runner; let added_runner;
it('registering as citizen should return 200', async () => { it('registering as citizen should return 200', async () => {
const res = await axios.post(base + '/api/runners/register', { const res = await axios.post(base + '/api/runners/register', {
@ -42,19 +42,19 @@ describe('POST /api/runners/me/login 2 times within timeout should fail', () =>
added_runner = res.data; added_runner = res.data;
}); });
it('post with valid mail should return 200', async () => { it('post with valid mail should return 200', async () => {
const res = await axios.post(base + '/api/runners/login?mail=' + added_runner.email, null, axios_config); const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
}); });
it('2nd post with valid mail should return 406', async () => { it('2nd post with valid mail should return 406', async () => {
const res = await axios.post(base + '/api/runners/login?mail=' + added_runner.email, null, axios_config); const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config);
expect(res.status).toEqual(406); expect(res.status).toEqual(406);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
}); });
}); });
// --------------- // ---------------
describe('POST /api/runners/me/login valid should return 200', () => { describe('POST /api/runners/me/forgot valid should return 200', () => {
let added_runner; let added_runner;
let new_token; let new_token;
it('registering as citizen should return 200', async () => { it('registering as citizen should return 200', async () => {
@ -69,7 +69,7 @@ describe('POST /api/runners/me/login valid should return 200', () => {
added_runner = res.data; added_runner = res.data;
}); });
it('post with valid mail should return 200', async () => { it('post with valid mail should return 200', async () => {
const res = await axios.post(base + '/api/runners/login?mail=' + added_runner.email, null, axios_config); const res = await axios.post(base + '/api/runners/forgot?mail=' + added_runner.email, null, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
new_token = res.data.token; new_token = res.data.token;

View File

@ -1,4 +1,3 @@
import { faker } from '@faker-js/faker';
import axios from 'axios'; import axios from 'axios';
import { config } from '../../config'; import { config } from '../../config';
const base = "http://localhost:" + config.internal_port const base = "http://localhost:" + config.internal_port
@ -31,7 +30,7 @@ describe('register + get should return 200', () => {
"firstname": "string", "firstname": "string",
"middlename": "string", "middlename": "string",
"lastname": "string", "lastname": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com"
}, axios_config); }, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");

View File

@ -1,4 +1,3 @@
import { faker } from '@faker-js/faker';
import axios from 'axios'; import axios from 'axios';
import { config } from '../../config'; import { config } from '../../config';
const base = "http://localhost:" + config.internal_port const base = "http://localhost:" + config.internal_port
@ -40,7 +39,7 @@ describe('register invalid citizen', () => {
const res = await axios.post(base + '/api/runners/register', { const res = await axios.post(base + '/api/runners/register', {
"middlename": "string", "middlename": "string",
"lastname": "string", "lastname": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com"
}, axios_config); }, axios_config);
expect(res.status).toEqual(400); expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
@ -49,7 +48,7 @@ describe('register invalid citizen', () => {
const res = await axios.post(base + '/api/runners/register', { const res = await axios.post(base + '/api/runners/register', {
"firstname": "string", "firstname": "string",
"middlename": "string", "middlename": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com"
}, axios_config); }, axios_config);
expect(res.status).toEqual(400); expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
@ -60,26 +59,7 @@ describe('register invalid citizen', () => {
"middlename": "string", "middlename": "string",
"lastname": "string", "lastname": "string",
"phone": "peter", "phone": "peter",
"email": faker.internet.exampleEmail(), "email": "user@example.com"
}, axios_config);
expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json");
});
it('registering as citizen with duplicate mail should return 400', async () => {
const mail = faker.internet.exampleEmail();
await axios.post(base + '/api/runners/register', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"phone": "peter",
"email": mail,
}, axios_config);
const res = await axios.post(base + '/api/runners/register', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"phone": "peter",
"email": mail,
}, axios_config); }, axios_config);
expect(res.status).toEqual(400); expect(res.status).toEqual(400);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
@ -91,7 +71,7 @@ describe('register citizen valid', () => {
const res = await axios.post(base + '/api/runners/register', { const res = await axios.post(base + '/api/runners/register', {
"firstname": "string", "firstname": "string",
"lastname": "string", "lastname": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com"
}, axios_config); }, axios_config);
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json"); expect(res.headers['content-type']).toContain("application/json");
@ -101,7 +81,7 @@ describe('register citizen valid', () => {
"firstname": "string", "firstname": "string",
"middlename": "string", "middlename": "string",
"lastname": "string", "lastname": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com",
"phone": "+4909132123456", "phone": "+4909132123456",
"address": { "address": {
address1: "Teststreet 1", address1: "Teststreet 1",
@ -207,7 +187,7 @@ describe('register valid company', () => {
"firstname": "string", "firstname": "string",
"middlename": "string", "middlename": "string",
"lastname": "string", "lastname": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com",
"phone": "+4909132123456", "phone": "+4909132123456",
"address": { "address": {
address1: "Teststreet 1", address1: "Teststreet 1",
@ -234,7 +214,7 @@ describe('register valid company', () => {
"firstname": "string", "firstname": "string",
"middlename": "string", "middlename": "string",
"lastname": "string", "lastname": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com",
"phone": "+4909132123456", "phone": "+4909132123456",
"address": { "address": {
address1: "Teststreet 1", address1: "Teststreet 1",
@ -252,7 +232,7 @@ describe('register valid company', () => {
"firstname": "string", "firstname": "string",
"middlename": "string", "middlename": "string",
"lastname": "string", "lastname": "string",
"email": faker.internet.exampleEmail(), "email": "user@example.com",
"phone": "+4909132123456", "phone": "+4909132123456",
"address": { "address": {
address1: "Teststreet 1", address1: "Teststreet 1",

View File

@ -1,89 +0,0 @@
import axios from 'axios';
import { config } from '../../config';
const base = "http://localhost:" + config.internal_port
let axios_config_full;
let axios_config_stats;
beforeAll(async () => {
jest.setTimeout(20000);
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
let access_token = res.data["access_token"];
axios_config_full = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined
};
const res2 = await axios.post(base + '/api/statsclients', { username: "demo", password: "demo" }, axios_config_full);
access_token = res2.data["key"];
axios_config_stats = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined
};
});
describe('GET /api/stats/runners/distance w/o auth should return 200', () => {
it('get with invalid token should return 401', async () => {
const res = await axios.get(base + '/api/stats/runners/distance', {
headers: { "authorization": "Bearer 123123123123123123" },
validateStatus: undefined
});
expect(res.status).toEqual(401);
});
});
// ---------------
describe('GET /api/stats should return 200', () => {
it('get w/o auth should return 200', async () => {
const res = await axios.get(base + '/api/stats', { validateStatus: undefined });
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('get w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
});
// ---------------
describe('GET /api/stats/runners/* should return 200', () => {
it('get by distance w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats/runners/distance', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('get by donations w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats/runners/donations', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('get by laptime w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats/runners/laptime', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
});
// ---------------
describe('GET /api/stats/teams/* should return 200', () => {
it('get by distance w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats/teams/distance', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('get by donations w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats/teams/donations', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
});
// ---------------
describe('GET /api/stats/organizations/* should return 200', () => {
it('get by distance w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats/organizations/distance', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
it('get by donations w/ auth should return 200', async () => {
const res = await axios.get(base + '/api/stats/organizations/donations', axios_config_stats);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
});

View File

@ -1,51 +0,0 @@
import axios from 'axios';
import { config } from '../../config';
const base = "http://localhost:" + config.internal_port
let access_token;
let axios_config;
beforeAll(async () => {
jest.setTimeout(20000);
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
access_token = res.data["access_token"];
axios_config = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined
};
});
// ---------------
describe('adding + deletion (non-existant)', () => {
it('delete', async () => {
const res2 = await axios.delete(base + '/api/users/0?force=true', axios_config);
expect(res2.status).toEqual(204);
});
});
// ---------------
describe('adding + deletion (successfull)', () => {
let added_user
it('valid user creation with minimal parameters should return 200', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "string",
"middlename": "string",
"lastname": "string",
"email": "demo_123_123_123asdASD@example.com",
"password": "demo_123_123_123asdASD",
"enabled": true
}
, axios_config);
added_user = res.data;
expect(res.status).toEqual(200);
});
it('delete', async () => {
const res2 = await axios.delete(base + '/api/users/' + added_user.id + "?force=true", axios_config);
expect(res2.status).toEqual(200);
expect(res2.headers['content-type']).toContain("application/json")
});
it('check if user really was deleted', async () => {
const res3 = await axios.get(base + '/api/users/' + added_user.id, axios_config);
expect(res3.status).toEqual(404);
expect(res3.headers['content-type']).toContain("application/json")
});
});

View File

@ -1,113 +0,0 @@
import axios from 'axios';
import { config } from '../../config';
const base = "http://localhost:" + config.internal_port
let axios_config = {};
beforeAll(async () => {
jest.setTimeout(20000);
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
let access_token = res.data["access_token"];
axios_config = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined
};
});
describe('POST /api/users valid', () => {
it('valid user creation with minimal parameters should return 200', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123",
"lastname": "demo_createASD123",
"password": "demo_createASD123",
"email": "demo_createASD123@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
});
it('valid user creation with all parameters should return 200', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_2",
"middlename": "demo_createASD123_2",
"lastname": "demo_createASD123_2",
"username": "demo_createASD123_2",
"password": "demo_createASD123_2",
"email": "demo_createASD123_2@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(200);
});
});
// ---------------
describe('POST /api/users invalid -> 400', () => {
it('user creation w/o firstname should return 400', async () => {
const res = await axios.post(base + '/api/users', {
"lastname": "demo_createASD123_3",
"password": "demo_createASD123_3",
"email": "demo_createASD123_3@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(400);
});
it('user creation w/o lastname should return 400', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_3",
"password": "demo_createASD123_3",
"email": "demo_createASD123_3@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(400);
});
it('user creation w/o password should return 400', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_3",
"lastname": "demo_createASD123_3",
"email": "demo_createASD123_3@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(400);
});
it('user creation w/o email should return 400', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_3",
"lastname": "demo_createASD123_3",
"password": "demo_createASD123_3"
}, axios_config);
expect(res.status).toEqual(400);
});
});
// ---------------
describe('POST /api/users invalid -> Password errors', () => {
it('user creation w/ invalid password -> No numbers should return 406', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_4",
"lastname": "demo_createASD123_4",
"password": "demo_createASD",
"email": "demo_createASD123_4@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(406);
});
it('user creation w/ invalid password -> No uppercase should return 406', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_4",
"lastname": "demo_createASD123_4",
"password": "demo_create_4",
"email": "demo_createASD123_4@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(406);
});
it('user creation w/ invalid password -> No lowercase should return 406', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_4",
"lastname": "demo_createASD123_4",
"password": "DEMO123123ASD",
"email": "demo_createASD123_4@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(406);
});
it('user creation w/ invalid password -> Too short should return 406', async () => {
const res = await axios.post(base + '/api/users', {
"firstname": "demo_createASD123_4",
"lastname": "demo_createASD123_4",
"password": "1Aa_",
"email": "demo_createASD123_4@dev.lauf-fuer-kaya.de"
}, axios_config);
expect(res.status).toEqual(406);
});
});