Compare commits
No commits in common. "main" and "0.0.6" have entirely different histories.
139
.drone.yml
139
.drone.yml
@ -1,42 +1,6 @@
|
|||||||
---
|
|
||||||
kind: secret
|
|
||||||
name: docker_username
|
|
||||||
get:
|
|
||||||
path: odit-registry-builder
|
|
||||||
name: username
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: secret
|
|
||||||
name: docker_password
|
|
||||||
get:
|
|
||||||
path: odit-registry-builder
|
|
||||||
name: password
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: secret
|
|
||||||
name: git_ssh
|
|
||||||
get:
|
|
||||||
path: odit-git-bot
|
|
||||||
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
|
name: tests:node_latest
|
||||||
name: tests:node
|
|
||||||
clone:
|
clone:
|
||||||
disable: true
|
disable: true
|
||||||
steps:
|
steps:
|
||||||
@ -45,48 +9,39 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- git clone $DRONE_REMOTE_URL .
|
- git clone $DRONE_REMOTE_URL .
|
||||||
- git checkout $DRONE_SOURCE_BRANCH
|
- git checkout $DRONE_SOURCE_BRANCH
|
||||||
|
- mv .env.ci .env
|
||||||
- name: run tests
|
- name: run tests
|
||||||
image: registry.odit.services/hub/library/node:19.5.0-alpine3.16
|
image: node:alpine
|
||||||
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
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: kubernetes
|
type: docker
|
||||||
name: build:dev
|
name: build:dev
|
||||||
clone:
|
|
||||||
disable: true
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: clone
|
|
||||||
image: alpine/git
|
|
||||||
commands:
|
|
||||||
- git clone $DRONE_REMOTE_URL .
|
|
||||||
- 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_REGISTRY_USER
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: DOCKER_REGISTRY_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
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- dev
|
||||||
|
event:
|
||||||
|
- push
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
@ -96,44 +51,22 @@ trigger:
|
|||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: kubernetes
|
type: docker
|
||||||
name: build:latest
|
name: build:latest
|
||||||
clone:
|
|
||||||
disable: true
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: clone
|
|
||||||
image: alpine/git
|
|
||||||
commands:
|
|
||||||
- git clone $DRONE_REMOTE_URL .
|
|
||||||
- git checkout dev
|
|
||||||
- git merge main
|
|
||||||
- git checkout main
|
|
||||||
- name: build latest
|
- name: build latest
|
||||||
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_REGISTRY_USER
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: DOCKER_REGISTRY_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
|
||||||
- name: push merge to repo
|
|
||||||
depends_on: ["clone"]
|
|
||||||
image: appleboy/drone-git-push
|
|
||||||
settings:
|
|
||||||
branch: dev
|
|
||||||
commit: false
|
|
||||||
remote: git@git.odit.services:lfk/backend.git
|
|
||||||
ssh_key:
|
|
||||||
from_secret: git_ssh
|
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
branch:
|
branch:
|
||||||
@ -143,32 +76,34 @@ trigger:
|
|||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: kubernetes
|
type: docker
|
||||||
name: build:tags
|
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_REGISTRY_USER
|
||||||
password:
|
password:
|
||||||
from_secret: docker_password
|
from_secret: DOCKER_REGISTRY_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
|
||||||
|
- 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
|
2
.env.ci
2
.env.ci
@ -6,4 +6,4 @@ DB_USER=unused
|
|||||||
DB_PASSWORD=bla
|
DB_PASSWORD=bla
|
||||||
DB_NAME=./test.sqlite
|
DB_NAME=./test.sqlite
|
||||||
NODE_ENV=dev
|
NODE_ENV=dev
|
||||||
POSTALCODE_COUNTRYCODE=DE
|
POSTALCODE_COUNTRYCODE=null
|
@ -1,10 +1,9 @@
|
|||||||
APP_PORT=4010
|
APP_PORT=4010
|
||||||
DB_TYPE=sqlite
|
DB_TYPE=bla
|
||||||
DB_HOST=bla
|
DB_HOST=bla
|
||||||
DB_PORT=bla
|
DB_PORT=bla
|
||||||
DB_USER=bla
|
DB_USER=bla
|
||||||
DB_PASSWORD=bla
|
DB_PASSWORD=bla
|
||||||
DB_NAME=./test.sqlite
|
DB_NAME=bla
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
POSTALCODE_COUNTRYCODE=DE
|
POSTALCODE_COUNTRYCODE=null
|
||||||
SEED_TEST_DATA=false
|
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -133,6 +133,4 @@ build
|
|||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite-jurnal
|
*.sqlite-jurnal
|
||||||
/docs
|
/docs
|
||||||
lib
|
lib
|
||||||
/oss-attribution
|
|
||||||
*.tmp
|
|
1856
CHANGELOG.md
1856
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
27
Dockerfile
27
Dockerfile
@ -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"]
|
|
115
README.md
115
README.md
@ -2,73 +2,39 @@
|
|||||||
|
|
||||||
Backend Server
|
Backend Server
|
||||||
|
|
||||||
## Quickstart 🐳
|
|
||||||
> Use this to run the backend with a postgresql db in docker
|
|
||||||
|
|
||||||
1. Clone the repo or copy the docker-compose
|
|
||||||
2. Run in toe folder that contains the docker-compose file: `docker-compose up -d`
|
|
||||||
3. Visit http://127.0.0.1:4010/api/docs to check if the server is running
|
|
||||||
4. You can now use the default admin user (`demo:demo`)
|
|
||||||
|
|
||||||
## Dev Setup 🛠
|
## Dev Setup 🛠
|
||||||
> Local dev setup utilizing sqlite3 as the database.
|
|
||||||
|
|
||||||
1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed)
|
### Local w/ sqlite
|
||||||
|
|
||||||
|
1. Create a .env file in the project root containing:
|
||||||
|
```
|
||||||
|
APP_PORT=4010
|
||||||
|
DB_TYPE=sqlite
|
||||||
|
DB_HOST=bla
|
||||||
|
DB_PORT=bla
|
||||||
|
DB_USER=bla
|
||||||
|
DB_PASSWORD=bla
|
||||||
|
DB_NAME=./test.sqlite
|
||||||
|
```
|
||||||
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
|
|
||||||
```bash
|
|
||||||
# Run tests once (server has to run)
|
|
||||||
pnpm test
|
|
||||||
|
|
||||||
# Run test in watch mode (reruns on change)
|
|
||||||
pnpm test:watch
|
|
||||||
|
|
||||||
# Run test in ci mode (automaticly starts the dev server)
|
|
||||||
pnpm test:ci
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use your own mail templates
|
|
||||||
> You use your own mail templates by replacing the default ones we provided (either in-code or by mounting them into the /app/static/mail_templates folder).
|
|
||||||
|
|
||||||
The mail templates always come in a .html and a .txt variant to provide compatability with legacy mail clients.
|
|
||||||
Currently the following templates exist:
|
|
||||||
* pw-reset.(html/txt)
|
|
||||||
|
|
||||||
### Generate Docs
|
### Generate Docs
|
||||||
```bash
|
```
|
||||||
pnpm docs
|
yarn docs
|
||||||
```
|
```
|
||||||
|
|
||||||
## ENV Vars
|
### Docker w/ postgres 🐳
|
||||||
> 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).
|
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
|
||||||
| ---------------------- | ------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| APP_PORT | Number | 4010 | The port the backend server listens on. Is optional. |
|
|
||||||
| DB_TYPE | String | N/A | The type of the db u want to use. It has to be supported by typeorm. Possible: `sqlite`, `mysql`, `postgresql` |
|
|
||||||
| DB_HOST | String | N/A | The db's host's ip-address/fqdn or file path for sqlite |
|
|
||||||
| DB_PORT | String | N/A | The db's port |
|
|
||||||
| DB_USER | String | N/A | The user for accessing the db |
|
|
||||||
| DB_PASSWORD | String | N/A | The user's password for accessing the db |
|
|
||||||
| DB_NAME | String | N/A | The db's name |
|
|
||||||
| NODE_ENV | String | dev | The apps env - influences debug info. 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 |
|
|
||||||
| 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 |
|
|
||||||
| MAILER_URL | String(Url) | N/A | The mailer's base url (no trailing slash) |
|
|
||||||
| 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) |
|
|
||||||
| PRIVACY_URL | String(Url) | /privacy | The link to a privacy page for the system (Defaults to the frontend's privacy page) |
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
## Recommended Editor
|
## Recommended Editor
|
||||||
|
|
||||||
@ -76,19 +42,22 @@ pnpm docs
|
|||||||
|
|
||||||
### Recommended Extensions
|
### Recommended Extensions
|
||||||
|
|
||||||
* will be automatically recommended via ./vscode/extensions.json
|
- will be automatically recommended via ./vscode/extensions.json
|
||||||
|
|
||||||
## Staging
|
## Branches
|
||||||
### Branches & Tags
|
- main: Protected "release" branch
|
||||||
* vX.Y.Z: Release tags created from the main branch
|
- dev: Current dev branch for merging the different features - only push for merges or minor changes!
|
||||||
* The version numbers follow the semver standard
|
- feature/xyz: Feature branches - `feature/issueid-title`
|
||||||
* A new release tag automaticly triggers the release ci pipeline
|
- bugfix/xyz: Branches for bugfixes - `bugfix/issueid-title` (no id for readme changes needed)
|
||||||
* main: Protected "release" branch
|
|
||||||
* The latest tag of the docker image get's build from this
|
|
||||||
* dev: Current dev branch for merging the different feature branches and bugfixes
|
## File Structure
|
||||||
* New releases get created as tags from this
|
|
||||||
* The dev tag of the docker image get's build from this
|
- src/models/entities\* - database models (typeorm entities)
|
||||||
* Only push minor changes to this branch!
|
- src/models/actions\* - actions models
|
||||||
* To merge a feature branch into this please create a pull request
|
- src/models/responses\* - response models
|
||||||
* feature/xyz: Feature branches - naming scheme: `feature/issueid-title`
|
- src/controllers/\* - routing-controllers
|
||||||
* bugfix/xyz: Branches for bugfixes - naming scheme:`bugfix/issueid-title`
|
- src/loaders/\* - loaders for the different init steps of the api server
|
||||||
|
- src/middlewares/\* - express middlewares (mainly auth r/n)
|
||||||
|
- src/errors/* - our custom (http) errors
|
||||||
|
- src/routes/\* - express routes for everything we don't do via routing-controllers (depreciated)
|
@ -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
|
||||||
|
1521
licenses.md
1521
licenses.md
File diff suppressed because it is too large
Load Diff
200
package.json
200
package.json
@ -1,117 +1,85 @@
|
|||||||
{
|
{
|
||||||
"name": "@odit/lfk-backend",
|
"name": "@odit/lfk-backend",
|
||||||
"version": "1.1.3",
|
"version": "0.0.6",
|
||||||
"main": "src/app.ts",
|
"main": "src/app.ts",
|
||||||
"repository": "https://git.odit.services/lfk/backend",
|
"repository": "https://git.odit.services/lfk/backend",
|
||||||
"engines": {
|
"author": {
|
||||||
"pnpm": "8"
|
"name": "ODIT.Services",
|
||||||
},
|
"email": "info@odit.services",
|
||||||
"author": {
|
"url": "https://odit.services"
|
||||||
"name": "ODIT.Services",
|
},
|
||||||
"email": "info@odit.services",
|
"contributors": [
|
||||||
"url": "https://odit.services"
|
{
|
||||||
},
|
"name": "Philipp Dormann",
|
||||||
"contributors": [
|
"email": "philipp@philippdormann.de",
|
||||||
{
|
"url": "https://philippdormann.de"
|
||||||
"name": "Philipp Dormann",
|
},
|
||||||
"email": "philipp@philippdormann.de",
|
{
|
||||||
"url": "https://philippdormann.de"
|
"name": "Nicolai Ort",
|
||||||
},
|
"email": "info@nicolai-ort.com",
|
||||||
{
|
"url": "https://nicolai-ort.com"
|
||||||
"name": "Nicolai Ort",
|
}
|
||||||
"email": "info@nicolai-ort.com",
|
],
|
||||||
"url": "https://nicolai-ort.com"
|
"license": "CC-BY-NC-SA-4.0",
|
||||||
}
|
"dependencies": {
|
||||||
],
|
"argon2": "^0.27.0",
|
||||||
"license": "CC-BY-NC-SA-4.0",
|
"body-parser": "^1.19.0",
|
||||||
"dependencies": {
|
"class-transformer": "^0.3.1",
|
||||||
"@odit/class-validator-jsonschema": "2.1.1",
|
"class-validator": "^0.12.2",
|
||||||
"argon2": "0.27.1",
|
"class-validator-jsonschema": "^2.0.3",
|
||||||
"axios": "0.21.1",
|
"consola": "^2.15.0",
|
||||||
"body-parser": "1.19.0",
|
"cookie": "^0.4.1",
|
||||||
"check-password-strength": "2.0.2",
|
"cookie-parser": "^1.4.5",
|
||||||
"class-transformer": "0.3.1",
|
"cors": "^2.8.5",
|
||||||
"class-validator": "0.13.1",
|
"csvtojson": "^2.0.10",
|
||||||
"consola": "2.15.0",
|
"dotenv": "^8.2.0",
|
||||||
"cookie": "0.4.1",
|
"express": "^4.17.1",
|
||||||
"cookie-parser": "1.4.5",
|
"jsonwebtoken": "^8.5.1",
|
||||||
"cors": "2.8.5",
|
"mysql": "^2.18.1",
|
||||||
"csvtojson": "2.0.10",
|
"pg": "^8.5.1",
|
||||||
"dotenv": "8.2.0",
|
"reflect-metadata": "^0.1.13",
|
||||||
"express": "4.17.1",
|
"routing-controllers": "^0.9.0-alpha.6",
|
||||||
"jsonwebtoken": "8.5.1",
|
"routing-controllers-openapi": "^2.1.0",
|
||||||
"libphonenumber-js": "1.9.9",
|
"sqlite3": "^5.0.0",
|
||||||
"mysql": "2.18.1",
|
"typeorm": "^0.2.29",
|
||||||
"pg": "8.5.1",
|
"typeorm-routing-controllers-extensions": "^0.2.0",
|
||||||
"reflect-metadata": "0.1.13",
|
"typeorm-seeding": "^1.6.1",
|
||||||
"routing-controllers": "0.9.0-alpha.6",
|
"uuid": "^8.3.1",
|
||||||
"routing-controllers-openapi": "2.2.0",
|
"validator": "^13.5.2"
|
||||||
"sqlite3": "5.0.0",
|
},
|
||||||
"typeorm": "0.2.30",
|
"devDependencies": {
|
||||||
"typeorm-routing-controllers-extensions": "0.2.0",
|
"@types/cors": "^2.8.8",
|
||||||
"typeorm-seeding": "1.6.1",
|
"@types/csvtojson": "^1.1.5",
|
||||||
"uuid": "8.3.2",
|
"@types/express": "^4.17.9",
|
||||||
"validator": "13.5.2"
|
"@types/jest": "^26.0.16",
|
||||||
},
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
"devDependencies": {
|
"@types/node": "^14.14.9",
|
||||||
"@faker-js/faker": "7.6.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@odit/license-exporter": "0.0.9",
|
"axios": "^0.21.0",
|
||||||
"@types/cors": "2.8.9",
|
"cp-cli": "^2.0.0",
|
||||||
"@types/csvtojson": "1.1.5",
|
"jest": "^26.6.3",
|
||||||
"@types/express": "4.17.11",
|
"nodemon": "^2.0.6",
|
||||||
"@types/jest": "26.0.20",
|
"rimraf": "^2.7.1",
|
||||||
"@types/jsonwebtoken": "8.5.0",
|
"start-server-and-test": "^1.11.6",
|
||||||
"@types/node": "14.14.22",
|
"ts-jest": "^26.4.4",
|
||||||
"@types/uuid": "8.3.0",
|
"ts-node": "^9.0.0",
|
||||||
"auto-changelog": "2.4.0",
|
"typedoc": "^0.19.2",
|
||||||
"cp-cli": "2.0.0",
|
"typescript": "^4.1.2"
|
||||||
"jest": "26.6.3",
|
},
|
||||||
"nodemon": "2.0.7",
|
"scripts": {
|
||||||
"release-it": "14.2.2",
|
"dev": "nodemon src/app.ts",
|
||||||
"rimraf": "3.0.2",
|
"build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static",
|
||||||
"start-server-and-test": "1.11.7",
|
"docs": "typedoc --out docs src",
|
||||||
"ts-jest": "26.5.0",
|
"test": "jest",
|
||||||
"ts-node": "9.1.1",
|
"test:watch": "jest --watchAll",
|
||||||
"typedoc": "0.20.19",
|
"test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
|
||||||
"typescript": "4.1.3"
|
"seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
|
||||||
},
|
"openapi:export": "ts-node src/openapi_export.ts"
|
||||||
"scripts": {
|
},
|
||||||
"dev": "nodemon src/app.ts",
|
"nodemonConfig": {
|
||||||
"build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static",
|
"ignore": [
|
||||||
"docs": "typedoc --out docs src",
|
"src/tests/*",
|
||||||
"test": "jest",
|
"docs/*"
|
||||||
"test:watch": "jest --watchAll",
|
]
|
||||||
"test:ci:generate_env": "ts-node scripts/create_testenv.ts",
|
}
|
||||||
"test:ci:run": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
|
|
||||||
"test:ci": "npm run test:ci:generate_env && npm run test:ci:run",
|
|
||||||
"seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
|
|
||||||
"openapi:export": "ts-node scripts/openapi_export.ts",
|
|
||||||
"licenses:export": "license-exporter --markdown",
|
|
||||||
"changelog:export": "auto-changelog --commit-limit false -p -u --hide-credit",
|
|
||||||
"release": "release-it --only-version"
|
|
||||||
},
|
|
||||||
"release-it": {
|
|
||||||
"git": {
|
|
||||||
"commit": true,
|
|
||||||
"requireCleanWorkingDir": false,
|
|
||||||
"commitMessage": "🚀Bumped version to v${version}",
|
|
||||||
"requireBranch": "dev",
|
|
||||||
"push": true,
|
|
||||||
"tag": true,
|
|
||||||
"tagName": "v${version}",
|
|
||||||
"tagAnnotation": "v${version}"
|
|
||||||
},
|
|
||||||
"npm": {
|
|
||||||
"publish": false
|
|
||||||
},
|
|
||||||
"hooks": {
|
|
||||||
"after:bump": "npm run changelog:export && npm run licenses:export && git add CHANGELOG.md && git add licenses.md"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nodemonConfig": {
|
|
||||||
"ignore": [
|
|
||||||
"src/tests/*",
|
|
||||||
"docs/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
7803
pnpm-lock.yaml
generated
7803
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
|||||||
import consola from "consola";
|
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
|
|
||||||
const env = `
|
|
||||||
APP_PORT=4010
|
|
||||||
DB_TYPE=sqlite
|
|
||||||
DB_HOST=bla
|
|
||||||
DB_PORT=bla
|
|
||||||
DB_USER=bla
|
|
||||||
DB_PASSWORD=bla
|
|
||||||
DB_NAME=./test.sqlite
|
|
||||||
NODE_ENV=test
|
|
||||||
POSTALCODE_COUNTRYCODE=DE
|
|
||||||
SEED_TEST_DATA=true
|
|
||||||
MAILER_URL=https://dev.lauf-fuer-kaya.de/mailer
|
|
||||||
MAILER_KEY=asdasd`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync("./.env", env, { encoding: "utf-8" });
|
|
||||||
consola.success("Exported ci env to .env");
|
|
||||||
} catch (error) {
|
|
||||||
consola.error("Couldn't export the ci env");
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import { validationMetadatasToSchemas } from '@odit/class-validator-jsonschema';
|
|
||||||
import consola from "consola";
|
|
||||||
import fs from "fs";
|
|
||||||
import "reflect-metadata";
|
|
||||||
import { createExpressServer, getMetadataArgsStorage } from "routing-controllers";
|
|
||||||
import { generateSpec } from '../src/apispec';
|
|
||||||
import { config } from '../src/config';
|
|
||||||
import authchecker from "../src/middlewares/authchecker";
|
|
||||||
import { ErrorHandler } from '../src/middlewares/ErrorHandler';
|
|
||||||
|
|
||||||
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
|
|
||||||
createExpressServer({
|
|
||||||
authorizationChecker: authchecker,
|
|
||||||
middlewares: [ErrorHandler],
|
|
||||||
development: config.development,
|
|
||||||
cors: true,
|
|
||||||
routePrefix: "/api",
|
|
||||||
controllers: [`${__dirname}/../src/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
|
|
||||||
});
|
|
||||||
|
|
||||||
const storage = getMetadataArgsStorage();
|
|
||||||
const schemas = validationMetadatasToSchemas({
|
|
||||||
refPointerPrefix: "#/components/schemas/",
|
|
||||||
});
|
|
||||||
|
|
||||||
//Spec creation based on the previously created schemas
|
|
||||||
const spec = generateSpec(storage, schemas);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync("./openapi.json", JSON.stringify(spec), { encoding: "utf-8" });
|
|
||||||
consola.success("Exported openapi spec to openapi.json");
|
|
||||||
} catch (error) {
|
|
||||||
consola.error("Couldn't export the openapi spec");
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
import { MetadataArgsStorage } from 'routing-controllers';
|
|
||||||
import { routingControllersToSpec } from 'routing-controllers-openapi';
|
|
||||||
import { config } from './config';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function generates a the openapi spec from route metadata and type schemas.
|
|
||||||
* @param storage MetadataArgsStorage object generated by routing-controllers.
|
|
||||||
* @param schemas MetadataArgsStorage object generated by class-validator-jsonschema.
|
|
||||||
*/
|
|
||||||
export function generateSpec(storage: MetadataArgsStorage, schemas) {
|
|
||||||
return routingControllersToSpec(
|
|
||||||
storage,
|
|
||||||
{
|
|
||||||
routePrefix: "/api"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
components: {
|
|
||||||
schemas,
|
|
||||||
"securitySchemes": {
|
|
||||||
"AuthToken": {
|
|
||||||
"type": "http",
|
|
||||||
"scheme": "bearer",
|
|
||||||
"bearerFormat": "JWT",
|
|
||||||
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
|
|
||||||
},
|
|
||||||
"RefreshTokenCookie": {
|
|
||||||
"type": "apiKey",
|
|
||||||
"in": "cookie",
|
|
||||||
"name": "lfk_backend__refresh_token",
|
|
||||||
description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
|
|
||||||
},
|
|
||||||
"StatsApiToken": {
|
|
||||||
"type": "http",
|
|
||||||
"scheme": "bearer",
|
|
||||||
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients). Only valid for obtaining stats."
|
|
||||||
},
|
|
||||||
"StationApiToken": {
|
|
||||||
"type": "http",
|
|
||||||
"scheme": "bearer",
|
|
||||||
description: "Api token that can be obtained by creating a new scan station (post to /api/stations). Only valid for creating scans."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
info: {
|
|
||||||
description: `The the backend API for the LfK! runner system. <br>[Imprint](${config.imprint_url}) & [Privacy](${config.privacy_url})`,
|
|
||||||
title: "LfK! Backend API",
|
|
||||||
version: config.version
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
@ -5,12 +5,10 @@ import { config, e as errors } from './config';
|
|||||||
import loaders from "./loaders/index";
|
import loaders from "./loaders/index";
|
||||||
import authchecker from "./middlewares/authchecker";
|
import authchecker from "./middlewares/authchecker";
|
||||||
import { ErrorHandler } from './middlewares/ErrorHandler';
|
import { ErrorHandler } from './middlewares/ErrorHandler';
|
||||||
import UserChecker from './middlewares/UserChecker';
|
|
||||||
|
|
||||||
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
|
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
|
||||||
const app = createExpressServer({
|
const app = createExpressServer({
|
||||||
authorizationChecker: authchecker,
|
authorizationChecker: authchecker,
|
||||||
currentUserChecker: UserChecker,
|
|
||||||
middlewares: [ErrorHandler],
|
middlewares: [ErrorHandler],
|
||||||
development: config.development,
|
development: config.development,
|
||||||
cors: true,
|
cors: true,
|
||||||
@ -20,9 +18,6 @@ const app = createExpressServer({
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
await loaders(app);
|
await loaders(app);
|
||||||
if (config.testing) {
|
|
||||||
consola.info("🛠[config]: Discovered testing env. Mailing errors will get ignored!")
|
|
||||||
}
|
|
||||||
app.listen(config.internal_port, () => {
|
app.listen(config.internal_port, () => {
|
||||||
consola.success(
|
consola.success(
|
||||||
`⚡️[server]: Server is running at http://localhost:${config.internal_port}`
|
`⚡️[server]: Server is running at http://localhost:${config.internal_port}`
|
||||||
|
@ -1,35 +1,26 @@
|
|||||||
import { config as configDotenv } from 'dotenv';
|
import { config as configDotenv } from 'dotenv';
|
||||||
import { CountryCode } from 'libphonenumber-js';
|
|
||||||
import ValidatorJS from 'validator';
|
import ValidatorJS from 'validator';
|
||||||
|
|
||||||
configDotenv();
|
configDotenv();
|
||||||
export const config = {
|
export const config = {
|
||||||
internal_port: parseInt(process.env.APP_PORT) || 4010,
|
internal_port: parseInt(process.env.APP_PORT) || 4010,
|
||||||
development: process.env.NODE_ENV === "production",
|
development: process.env.NODE_ENV === "production",
|
||||||
testing: process.env.NODE_ENV === "test",
|
|
||||||
jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
|
jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
|
||||||
phone_validation_countrycode: getPhoneCodeLocale(),
|
phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ",
|
||||||
postalcode_validation_countrycode: getPostalCodeLocale(),
|
postalcode_validation_countrycode: getPostalCodeLocale()
|
||||||
version: process.env.VERSION || require('../package.json').version,
|
|
||||||
seedTestData: getDataSeeding(),
|
|
||||||
app_url: process.env.APP_URL || "http://localhost:8080",
|
|
||||||
privacy_url: process.env.PRIVACY_URL || "/privacy",
|
|
||||||
imprint_url: process.env.IMPRINT_URL || "/imprint",
|
|
||||||
mailer_url: process.env.MAILER_URL || "",
|
|
||||||
mailer_key: process.env.MAILER_KEY || ""
|
|
||||||
}
|
}
|
||||||
let errors = 0
|
let errors = 0
|
||||||
if (typeof config.internal_port !== "number") {
|
if (typeof config.internal_port !== "number") {
|
||||||
errors++
|
errors++
|
||||||
}
|
}
|
||||||
if (typeof config.development !== "boolean") {
|
if (typeof config.phone_validation_countrycode !== "string") {
|
||||||
errors++
|
errors++
|
||||||
}
|
}
|
||||||
if (config.mailer_url == "" || config.mailer_key == "") {
|
if (config.phone_validation_countrycode.length !== 2) {
|
||||||
errors++;
|
errors++
|
||||||
}
|
}
|
||||||
function getPhoneCodeLocale(): CountryCode {
|
if (typeof config.development !== "boolean") {
|
||||||
return (process.env.PHONE_COUNTRYCODE as CountryCode);
|
errors++
|
||||||
}
|
}
|
||||||
function getPostalCodeLocale(): any {
|
function getPostalCodeLocale(): any {
|
||||||
try {
|
try {
|
||||||
@ -40,11 +31,4 @@ function getPostalCodeLocale(): any {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getDataSeeding(): Boolean {
|
|
||||||
try {
|
|
||||||
return JSON.parse(process.env.SEED_TEST_DATA);
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export let e = errors
|
export let e = errors
|
@ -1,106 +1,104 @@
|
|||||||
import { Body, CookieParam, JsonController, Param, Post, QueryParam, Req, Res } from 'routing-controllers';
|
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||||
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
|
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
|
||||||
import { MailSendingError } from '../errors/MailErrors';
|
import { UserNotFoundError } from '../errors/UserErrors';
|
||||||
import { UserNotFoundError } from '../errors/UserErrors';
|
import { CreateAuth } from '../models/actions/CreateAuth';
|
||||||
import { Mailer } from '../mailer';
|
import { CreateResetToken } from '../models/actions/CreateResetToken';
|
||||||
import { CreateAuth } from '../models/actions/create/CreateAuth';
|
import { HandleLogout } from '../models/actions/HandleLogout';
|
||||||
import { CreateResetToken } from '../models/actions/create/CreateResetToken';
|
import { RefreshAuth } from '../models/actions/RefreshAuth';
|
||||||
import { HandleLogout } from '../models/actions/HandleLogout';
|
import { ResetPassword } from '../models/actions/ResetPassword';
|
||||||
import { RefreshAuth } from '../models/actions/RefreshAuth';
|
import { Auth } from '../models/responses/ResponseAuth';
|
||||||
import { ResetPassword } from '../models/actions/ResetPassword';
|
import { Logout } from '../models/responses/ResponseLogout';
|
||||||
import { ResponseAuth } from '../models/responses/ResponseAuth';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
@JsonController('/auth')
|
||||||
import { Logout } from '../models/responses/ResponseLogout';
|
export class AuthController {
|
||||||
|
constructor() {
|
||||||
@JsonController('/auth')
|
}
|
||||||
export class AuthController {
|
|
||||||
|
@Post("/login")
|
||||||
@Post("/login")
|
@ResponseSchema(Auth)
|
||||||
@ResponseSchema(ResponseAuth)
|
@ResponseSchema(InvalidCredentialsError)
|
||||||
@ResponseSchema(InvalidCredentialsError)
|
@ResponseSchema(UserNotFoundError)
|
||||||
@ResponseSchema(UserNotFoundError)
|
@ResponseSchema(UsernameOrEmailNeededError)
|
||||||
@ResponseSchema(UsernameOrEmailNeededError)
|
@ResponseSchema(PasswordNeededError)
|
||||||
@ResponseSchema(PasswordNeededError)
|
@ResponseSchema(InvalidCredentialsError)
|
||||||
@ResponseSchema(InvalidCredentialsError)
|
@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
|
||||||
@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
|
async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
|
||||||
async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
|
let auth;
|
||||||
let auth;
|
try {
|
||||||
try {
|
auth = await createAuth.toAuth();
|
||||||
auth = await createAuth.toAuth();
|
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
||||||
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
||||||
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
return response.send(auth)
|
||||||
return response.send(auth)
|
} catch (error) {
|
||||||
} catch (error) {
|
throw error;
|
||||||
throw error;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Post("/logout")
|
||||||
@Post("/logout")
|
@ResponseSchema(Logout)
|
||||||
@ResponseSchema(Logout)
|
@ResponseSchema(InvalidCredentialsError)
|
||||||
@ResponseSchema(InvalidCredentialsError)
|
@ResponseSchema(UserNotFoundError)
|
||||||
@ResponseSchema(UserNotFoundError)
|
@ResponseSchema(UsernameOrEmailNeededError)
|
||||||
@ResponseSchema(UsernameOrEmailNeededError)
|
@ResponseSchema(PasswordNeededError)
|
||||||
@ResponseSchema(PasswordNeededError)
|
@ResponseSchema(InvalidCredentialsError)
|
||||||
@ResponseSchema(InvalidCredentialsError)
|
@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
|
||||||
@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
|
async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
|
||||||
async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
|
if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
|
||||||
if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
|
handleLogout.token = refresh_token;
|
||||||
handleLogout.token = refresh_token;
|
}
|
||||||
}
|
|
||||||
|
let logout;
|
||||||
let logout;
|
try {
|
||||||
try {
|
logout = await handleLogout.logout()
|
||||||
logout = await handleLogout.logout()
|
await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true });
|
||||||
await response.cookie('lfk_backend__refresh_token', "expired", { expires: new Date(Date.now()), httpOnly: true });
|
response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true });
|
||||||
response.cookie('lfk_backend__refresh_token_expires_at', "expired", { expires: new Date(Date.now()), httpOnly: true });
|
} catch (error) {
|
||||||
} catch (error) {
|
throw error;
|
||||||
throw error;
|
}
|
||||||
}
|
return response.send(logout)
|
||||||
return response.send(logout)
|
}
|
||||||
}
|
|
||||||
|
@Post("/refresh")
|
||||||
@Post("/refresh")
|
@ResponseSchema(Auth)
|
||||||
@ResponseSchema(ResponseAuth)
|
@ResponseSchema(JwtNotProvidedError)
|
||||||
@ResponseSchema(JwtNotProvidedError)
|
@ResponseSchema(IllegalJWTError)
|
||||||
@ResponseSchema(IllegalJWTError)
|
@ResponseSchema(UserNotFoundError)
|
||||||
@ResponseSchema(UserNotFoundError)
|
@ResponseSchema(RefreshTokenCountInvalidError)
|
||||||
@ResponseSchema(RefreshTokenCountInvalidError)
|
@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
|
||||||
@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
|
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
|
||||||
async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
|
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
|
||||||
if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
|
refreshAuth.token = refresh_token;
|
||||||
refreshAuth.token = refresh_token;
|
}
|
||||||
}
|
console.log(req.headers)
|
||||||
let auth;
|
let auth;
|
||||||
try {
|
try {
|
||||||
auth = await refreshAuth.toAuth();
|
auth = await refreshAuth.toAuth();
|
||||||
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
response.cookie('lfk_backend__refresh_token', auth.refresh_token, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
||||||
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
response.cookie('lfk_backend__refresh_token_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
return response.send(auth)
|
return response.send(auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post("/reset")
|
@Post("/reset")
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 200 })
|
@ResponseSchema(Auth)
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
@ResponseSchema(UserNotFoundError)
|
||||||
@ResponseSchema(UsernameOrEmailNeededError, { statusCode: 406 })
|
@ResponseSchema(UsernameOrEmailNeededError)
|
||||||
@ResponseSchema(MailSendingError, { statusCode: 500 })
|
@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
|
||||||
@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
|
async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) {
|
||||||
async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken, @QueryParam("locale") locale: string = "en") {
|
//This really shouldn't just get returned, but sent via mail or sth like that. But for dev only this is fine.
|
||||||
const reset_token: string = await passwordReset.toResetToken();
|
return { "resetToken": await passwordReset.toResetToken() };
|
||||||
await Mailer.sendResetMail(passwordReset.email, reset_token, locale);
|
}
|
||||||
return new ResponseEmpty();
|
|
||||||
}
|
@Post("/reset/:token")
|
||||||
|
@ResponseSchema(Auth)
|
||||||
@Post("/reset/:token")
|
@ResponseSchema(UserNotFoundError)
|
||||||
@ResponseSchema(ResponseAuth)
|
@ResponseSchema(UsernameOrEmailNeededError)
|
||||||
@ResponseSchema(UserNotFoundError)
|
@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
|
||||||
@ResponseSchema(UsernameOrEmailNeededError)
|
async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
|
||||||
@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
|
passwordReset.resetToken = token;
|
||||||
async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
|
return await passwordReset.resetPassword();
|
||||||
passwordReset.resetToken = token;
|
}
|
||||||
return await passwordReset.resetPassword();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { Repository, getConnectionManager } from 'typeorm';
|
|
||||||
import { DonationIdsNotMatchingError, DonationNotFoundError } from '../errors/DonationErrors';
|
|
||||||
import { DonorNotFoundError } from '../errors/DonorErrors';
|
|
||||||
import { RunnerNotFoundError } from '../errors/RunnerErrors';
|
|
||||||
import { CreateDistanceDonation } from '../models/actions/create/CreateDistanceDonation';
|
|
||||||
import { CreateFixedDonation } from '../models/actions/create/CreateFixedDonation';
|
|
||||||
import { UpdateDistanceDonation } from '../models/actions/update/UpdateDistanceDonation';
|
|
||||||
import { UpdateFixedDonation } from '../models/actions/update/UpdateFixedDonation';
|
|
||||||
import { DistanceDonation } from '../models/entities/DistanceDonation';
|
|
||||||
import { Donation } from '../models/entities/Donation';
|
|
||||||
import { FixedDonation } from '../models/entities/FixedDonation';
|
|
||||||
import { ResponseDistanceDonation } from '../models/responses/ResponseDistanceDonation';
|
|
||||||
import { ResponseDonation } from '../models/responses/ResponseDonation';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
|
|
||||||
@JsonController('/donations')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class DonationController {
|
|
||||||
private donationRepository: Repository<Donation>;
|
|
||||||
private distanceDonationRepository: Repository<DistanceDonation>;
|
|
||||||
private fixedDonationRepository: Repository<FixedDonation>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.donationRepository = getConnectionManager().get().getRepository(Donation);
|
|
||||||
this.distanceDonationRepository = getConnectionManager().get().getRepository(DistanceDonation);
|
|
||||||
this.fixedDonationRepository = getConnectionManager().get().getRepository(FixedDonation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@Authorized("DONATION:GET")
|
|
||||||
@ResponseSchema(ResponseDonation, { isArray: true })
|
|
||||||
@ResponseSchema(ResponseDistanceDonation, { isArray: true })
|
|
||||||
@OpenAPI({ description: 'Lists all donations (fixed or distance based) from all donors. <br> This includes the donations\'s runner\'s distance ran(if distance donation).' })
|
|
||||||
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
|
|
||||||
let responseDonations: ResponseDonation[] = new Array<ResponseDonation>();
|
|
||||||
let donations: Array<Donation>;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
responseDonations.push(donation.toResponse());
|
|
||||||
});
|
|
||||||
return responseDonations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
@Authorized("DONATION:GET")
|
|
||||||
@ResponseSchema(ResponseDonation)
|
|
||||||
@ResponseSchema(ResponseDistanceDonation)
|
|
||||||
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(DonationNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about the donation whose id got provided. This includes the donation\'s runner\'s distance ran (if distance donation).' })
|
|
||||||
async getOne(@Param('id') id: number) {
|
|
||||||
let donation = await this.donationRepository.findOne({ id: id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })
|
|
||||||
if (!donation) { throw new DonationNotFoundError(); }
|
|
||||||
return donation.toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/fixed')
|
|
||||||
@Authorized("DONATION:CREATE")
|
|
||||||
@ResponseSchema(ResponseDonation)
|
|
||||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Create a fixed donation (not distance donation - use /donations/distance instead). <br> Please rmemember to provide the donation\'s donors\'s id and amount.' })
|
|
||||||
async postFixed(@Body({ validate: true }) createDonation: CreateFixedDonation) {
|
|
||||||
let donation = await createDonation.toEntity();
|
|
||||||
donation = await this.fixedDonationRepository.save(donation);
|
|
||||||
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/distance')
|
|
||||||
@Authorized("DONATION:CREATE")
|
|
||||||
@ResponseSchema(ResponseDistanceDonation)
|
|
||||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Create a distance donation (not fixed donation - use /donations/fixed instead). <br> Please rmemember to provide the donation\'s donors\'s and runner\s ids and amount per distance (kilometer).' })
|
|
||||||
async postDistance(@Body({ validate: true }) createDonation: CreateDistanceDonation) {
|
|
||||||
let donation = await createDonation.toEntity();
|
|
||||||
donation = await this.distanceDonationRepository.save(donation);
|
|
||||||
return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/fixed/:id')
|
|
||||||
@Authorized("DONATION:UPDATE")
|
|
||||||
@ResponseSchema(ResponseDonation)
|
|
||||||
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the fixed donation (not distance donation - use /donations/distance instead) whose id you provided. <br> Please remember that ids can't be changed and amounts must be positive." })
|
|
||||||
async putFixed(@Param('id') id: number, @Body({ validate: true }) donation: UpdateFixedDonation) {
|
|
||||||
let oldDonation = await this.fixedDonationRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldDonation) {
|
|
||||||
throw new DonationNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldDonation.id != donation.id) {
|
|
||||||
throw new DonationIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.fixedDonationRepository.save(await donation.update(oldDonation));
|
|
||||||
return (await this.donationRepository.findOne({ id: donation.id }, { relations: ['donor'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/distance/:id')
|
|
||||||
@Authorized("DONATION:UPDATE")
|
|
||||||
@ResponseSchema(ResponseDonation)
|
|
||||||
@ResponseSchema(DonationNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(DonationIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the distance donation (not fixed donation - use /donations/fixed instead) whose id you provided. <br> Please remember that ids can't be changed and amountPerDistance must be positive." })
|
|
||||||
async putDistance(@Param('id') id: number, @Body({ validate: true }) donation: UpdateDistanceDonation) {
|
|
||||||
let oldDonation = await this.distanceDonationRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldDonation) {
|
|
||||||
throw new DonationNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldDonation.id != donation.id) {
|
|
||||||
throw new DonationIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.distanceDonationRepository.save(await donation.update(oldDonation));
|
|
||||||
return (await this.distanceDonationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/:id')
|
|
||||||
@Authorized("DONATION:DELETE")
|
|
||||||
@ResponseSchema(ResponseDonation)
|
|
||||||
@ResponseSchema(ResponseDistanceDonation)
|
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
|
||||||
@OnUndefined(204)
|
|
||||||
@OpenAPI({ description: 'Delete the donation whose id you provided. <br> If no donation with this id exists it will just return 204(no content).' })
|
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
|
||||||
let donation = await this.donationRepository.findOne({ id: id });
|
|
||||||
if (!donation) { return null; }
|
|
||||||
const responseScan = await this.donationRepository.findOne({ id: donation.id }, { relations: ['runner', 'donor', 'runner.scans', 'runner.scans.track'] });
|
|
||||||
|
|
||||||
await this.donationRepository.delete(donation);
|
|
||||||
return responseScan.toResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,120 +0,0 @@
|
|||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { Repository, getConnectionManager } from 'typeorm';
|
|
||||||
import { DonorHasDonationsError, DonorIdsNotMatchingError, DonorNotFoundError } from '../errors/DonorErrors';
|
|
||||||
import { CreateDonor } from '../models/actions/create/CreateDonor';
|
|
||||||
import { UpdateDonor } from '../models/actions/update/UpdateDonor';
|
|
||||||
import { Donor } from '../models/entities/Donor';
|
|
||||||
import { ResponseDonor } from '../models/responses/ResponseDonor';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
import { DonationController } from './DonationController';
|
|
||||||
|
|
||||||
@JsonController('/donors')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class DonorController {
|
|
||||||
private donorRepository: Repository<Donor>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.donorRepository = getConnectionManager().get().getRepository(Donor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@Authorized("DONOR:GET")
|
|
||||||
@ResponseSchema(ResponseDonor, { isArray: true })
|
|
||||||
@OpenAPI({ description: 'Lists all donor. <br> This includes the donor\'s current donation amount.' })
|
|
||||||
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
|
|
||||||
let responseDonors: ResponseDonor[] = new Array<ResponseDonor>();
|
|
||||||
let donors: Array<Donor>;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
responseDonors.push(new ResponseDonor(donor));
|
|
||||||
});
|
|
||||||
return responseDonors;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
@Authorized("DONOR:GET")
|
|
||||||
@ResponseSchema(ResponseDonor)
|
|
||||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(DonorNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about the donor whose id got provided. <br> This includes the donor\'s current donation amount.' })
|
|
||||||
async getOne(@Param('id') id: number) {
|
|
||||||
let donor = await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] })
|
|
||||||
if (!donor) { throw new DonorNotFoundError(); }
|
|
||||||
return new ResponseDonor(donor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@Authorized("DONOR:CREATE")
|
|
||||||
@ResponseSchema(ResponseDonor)
|
|
||||||
@OpenAPI({ description: 'Create a new donor.' })
|
|
||||||
async post(@Body({ validate: true }) createRunner: CreateDonor) {
|
|
||||||
let donor;
|
|
||||||
try {
|
|
||||||
donor = await createRunner.toEntity();
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
donor = await this.donorRepository.save(donor)
|
|
||||||
return new ResponseDonor(await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/:id')
|
|
||||||
@Authorized("DONOR:UPDATE")
|
|
||||||
@ResponseSchema(ResponseDonor)
|
|
||||||
@ResponseSchema(DonorNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(DonorIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the donor whose id you provided. <br> Please remember that ids can't be changed." })
|
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) donor: UpdateDonor) {
|
|
||||||
let oldDonor = await this.donorRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldDonor) {
|
|
||||||
throw new DonorNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldDonor.id != donor.id) {
|
|
||||||
throw new DonorIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.donorRepository.save(await donor.update(oldDonor));
|
|
||||||
return new ResponseDonor(await this.donorRepository.findOne({ id: id }, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] }));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/:id')
|
|
||||||
@Authorized("DONOR:DELETE")
|
|
||||||
@ResponseSchema(ResponseDonor)
|
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
|
||||||
@OnUndefined(204)
|
|
||||||
@OpenAPI({ description: 'Delete the donor whose id you provided. <br> If no donor with this id exists it will just return 204(no content). <br> If the donor still has donations associated this will fail, please provide the query param ?force=true to delete the donor with all associated donations.' })
|
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
|
||||||
let donor = await this.donorRepository.findOne({ id: id });
|
|
||||||
if (!donor) { return null; }
|
|
||||||
const responseDonor = await this.donorRepository.findOne(donor, { relations: ['donations', 'donations.runner', 'donations.runner.scans', 'donations.runner.scans.track'] });
|
|
||||||
|
|
||||||
if (!donor) {
|
|
||||||
throw new DonorNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const donorDonations = (await this.donorRepository.findOne({ id: donor.id }, { relations: ["donations"] })).donations;
|
|
||||||
if (donorDonations.length > 0 && !force) {
|
|
||||||
throw new DonorHasDonationsError();
|
|
||||||
}
|
|
||||||
const donationController = new DonationController();
|
|
||||||
for (let donation of donorDonations) {
|
|
||||||
await donationController.remove(donation.id, force);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.donorRepository.delete(donor);
|
|
||||||
return new ResponseDonor(responseDonor);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { Repository, getConnection, getConnectionManager } from 'typeorm';
|
|
||||||
import { GroupContactIdsNotMatchingError, GroupContactNotFoundError } from '../errors/GroupContactErrors';
|
|
||||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
|
|
||||||
import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
|
|
||||||
import { UpdateGroupContact } from '../models/actions/update/UpdateGroupContact';
|
|
||||||
import { GroupContact } from '../models/entities/GroupContact';
|
|
||||||
import { RunnerGroup } from '../models/entities/RunnerGroup';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
import { ResponseGroupContact } from '../models/responses/ResponseGroupContact';
|
|
||||||
|
|
||||||
@JsonController('/contacts')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class GroupContactController {
|
|
||||||
private contactRepository: Repository<GroupContact>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.contactRepository = getConnectionManager().get().getRepository(GroupContact);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@Authorized("CONTACT:GET")
|
|
||||||
@ResponseSchema(ResponseGroupContact, { isArray: true })
|
|
||||||
@OpenAPI({ description: 'Lists all contacts. <br> This includes the contact\'s associated groups.' })
|
|
||||||
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
|
|
||||||
let responseContacts: ResponseGroupContact[] = new Array<ResponseGroupContact>();
|
|
||||||
let contacts: Array<GroupContact>;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
responseContacts.push(contact.toResponse());
|
|
||||||
});
|
|
||||||
return responseContacts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
@Authorized("CONTACT:GET")
|
|
||||||
@ResponseSchema(ResponseGroupContact)
|
|
||||||
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(GroupContactNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about the contact whose id got provided. <br> This includes the contact\'s associated groups.' })
|
|
||||||
async getOne(@Param('id') id: number) {
|
|
||||||
let contact = await this.contactRepository.findOne({ id: id }, { relations: ['groups', 'groups.parentGroup'] })
|
|
||||||
if (!contact) { throw new GroupContactNotFoundError(); }
|
|
||||||
return contact.toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@Authorized("CONTACT:CREATE")
|
|
||||||
@ResponseSchema(ResponseGroupContact)
|
|
||||||
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Create a new contact.' })
|
|
||||||
async post(@Body({ validate: true }) createContact: CreateGroupContact) {
|
|
||||||
let contact;
|
|
||||||
try {
|
|
||||||
contact = await createContact.toEntity();
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
contact = await this.contactRepository.save(contact)
|
|
||||||
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/:id')
|
|
||||||
@Authorized("CONTACT:UPDATE")
|
|
||||||
@ResponseSchema(ResponseGroupContact)
|
|
||||||
@ResponseSchema(GroupContactNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(GroupContactIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: "Update the contact whose id you provided. <br> Please remember that ids can't be changed." })
|
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) contact: UpdateGroupContact) {
|
|
||||||
let oldContact = await this.contactRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldContact) {
|
|
||||||
throw new GroupContactNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldContact.id != contact.id) {
|
|
||||||
throw new GroupContactIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.contactRepository.save(await contact.update(oldContact));
|
|
||||||
return (await this.contactRepository.findOne({ id: contact.id }, { relations: ['groups', 'groups.parentGroup'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/:id')
|
|
||||||
@Authorized("CONTACT:DELETE")
|
|
||||||
@ResponseSchema(ResponseGroupContact)
|
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
|
||||||
@OnUndefined(204)
|
|
||||||
@OpenAPI({ description: 'Delete the contact whose id you provided. <br> If no contact with this id exists it will just return 204(no content). <br> This won\'t delete any groups associated with the contact.' })
|
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
|
||||||
let contact = await this.contactRepository.findOne({ id: id });
|
|
||||||
if (!contact) { return null; }
|
|
||||||
const responseContact = await this.contactRepository.findOne(contact, { relations: ['groups', 'groups.parentGroup'] });
|
|
||||||
for (let group of responseContact.groups) {
|
|
||||||
group.contact = null;
|
|
||||||
await getConnection().getRepository(RunnerGroup).save(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.contactRepository.delete(contact);
|
|
||||||
return responseContact.toResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -36,7 +36,7 @@ export class ImportController {
|
|||||||
return responseRunners;
|
return responseRunners;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/organizations/:id/import')
|
@Post('/organisations/:id/import')
|
||||||
@ContentType("application/json")
|
@ContentType("application/json")
|
||||||
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
|
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
|
||||||
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
|
@ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
|
||||||
@ -78,7 +78,7 @@ export class ImportController {
|
|||||||
return await this.postJSON(importRunners, groupID);
|
return await this.postJSON(importRunners, groupID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('/organizations/:id/import/csv')
|
@Post('/organisations/:id/import/csv')
|
||||||
@ContentType("application/json")
|
@ContentType("application/json")
|
||||||
@UseBefore(RawBodyMiddleware)
|
@UseBefore(RawBodyMiddleware)
|
||||||
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
|
@ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
|
||||||
|
@ -1,90 +0,0 @@
|
|||||||
import { Body, CurrentUser, Delete, Get, JsonController, OnUndefined, Put, QueryParam } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { getConnectionManager, Repository } from 'typeorm';
|
|
||||||
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserDeletionNotConfirmedError, UserIdsNotMatchingError, UsernameContainsIllegalCharacterError, UserNotFoundError } from '../errors/UserErrors';
|
|
||||||
import { UpdateUser } from '../models/actions/update/UpdateUser';
|
|
||||||
import { User } from '../models/entities/User';
|
|
||||||
import { ResponseUser } from '../models/responses/ResponseUser';
|
|
||||||
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
|
|
||||||
import { PermissionController } from './PermissionController';
|
|
||||||
|
|
||||||
|
|
||||||
@JsonController('/users/me')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class MeController {
|
|
||||||
private userRepository: Repository<User>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.userRepository = getConnectionManager().get().getRepository(User);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/')
|
|
||||||
@ResponseSchema(ResponseUser)
|
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(UserNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about yourself.' })
|
|
||||||
async get(@CurrentUser() currentUser: User) {
|
|
||||||
let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
|
|
||||||
if (!user) { throw new UserNotFoundError(); }
|
|
||||||
return new ResponseUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/permissions')
|
|
||||||
@ResponseSchema(ResponseUserPermissions)
|
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(UserNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all permissions granted to the you sorted into directly granted and inherited as permission response objects.' })
|
|
||||||
async getPermissions(@CurrentUser() currentUser: User) {
|
|
||||||
let user = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
|
|
||||||
if (!user) { throw new UserNotFoundError(); }
|
|
||||||
return new ResponseUserPermissions(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/')
|
|
||||||
@ResponseSchema(ResponseUser)
|
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@ResponseSchema(UsernameContainsIllegalCharacterError, { statusCode: 406 })
|
|
||||||
@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." })
|
|
||||||
async put(@CurrentUser() currentUser: User, @Body({ validate: true }) updateUser: UpdateUser) {
|
|
||||||
let oldUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['groups'] });
|
|
||||||
updateUser.groups = oldUser.groups.map(g => g.id);
|
|
||||||
|
|
||||||
if (!oldUser) {
|
|
||||||
throw new UserNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldUser.id != updateUser.id) {
|
|
||||||
throw new UserIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
await this.userRepository.save(await updateUser.update(oldUser));
|
|
||||||
|
|
||||||
return new ResponseUser(await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/')
|
|
||||||
@ResponseSchema(ResponseUser)
|
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: 'Delete yourself. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to you they will get deleted as well.' })
|
|
||||||
async remove(@CurrentUser() currentUser: User, @QueryParam("force") force: boolean) {
|
|
||||||
if (!force) { throw new UserDeletionNotConfirmedError; }
|
|
||||||
if (!currentUser) { return UserNotFoundError; }
|
|
||||||
const responseUser = await this.userRepository.findOne({ id: currentUser.id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
|
|
||||||
|
|
||||||
const permissionControler = new PermissionController();
|
|
||||||
for (let permission of responseUser.permissions) {
|
|
||||||
await permissionControler.remove(permission.id, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.userRepository.delete(currentUser);
|
|
||||||
return new ResponseUser(responseUser);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +1,10 @@
|
|||||||
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/CreatePermission';
|
||||||
import { UpdatePermission } from '../models/actions/update/UpdatePermission';
|
import { UpdatePermission } from '../models/actions/UpdatePermission';
|
||||||
import { Permission } from '../models/entities/Permission';
|
import { Permission } from '../models/entities/Permission';
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
import { ResponsePermission } from '../models/responses/ResponsePermission';
|
import { ResponsePermission } from '../models/responses/ResponsePermission';
|
||||||
@ -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));
|
||||||
});
|
});
|
||||||
@ -65,7 +58,7 @@ export class PermissionController {
|
|||||||
async post(@Body({ validate: true }) createPermission: CreatePermission) {
|
async post(@Body({ validate: true }) createPermission: CreatePermission) {
|
||||||
let permission;
|
let permission;
|
||||||
try {
|
try {
|
||||||
permission = await createPermission.toEntity();
|
permission = await createPermission.toPermission();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -97,13 +90,13 @@ export class PermissionController {
|
|||||||
if (oldPermission.id != permission.id) {
|
if (oldPermission.id != permission.id) {
|
||||||
throw new PermissionIdsNotMatchingError();
|
throw new PermissionIdsNotMatchingError();
|
||||||
}
|
}
|
||||||
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: await permission.getPrincipal() }, { relations: ['principal'] });
|
let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] });
|
||||||
if (existingPermission) {
|
if (existingPermission) {
|
||||||
await this.remove(permission.id, true);
|
await this.remove(permission.id, true);
|
||||||
return new ResponsePermission(existingPermission);
|
return new ResponsePermission(existingPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.permissionRepository.save(await permission.update(oldPermission));
|
await this.permissionRepository.save(await permission.updatePermission(oldPermission));
|
||||||
|
|
||||||
return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
|
return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
|
||||||
}
|
}
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { Repository, getConnectionManager } from 'typeorm';
|
|
||||||
import { RunnerCardHasScansError, RunnerCardIdsNotMatchingError, RunnerCardNotFoundError } from '../errors/RunnerCardErrors';
|
|
||||||
import { RunnerNotFoundError } from '../errors/RunnerErrors';
|
|
||||||
import { CreateRunnerCard } from '../models/actions/create/CreateRunnerCard';
|
|
||||||
import { UpdateRunnerCard } from '../models/actions/update/UpdateRunnerCard';
|
|
||||||
import { RunnerCard } from '../models/entities/RunnerCard';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
import { ResponseRunnerCard } from '../models/responses/ResponseRunnerCard';
|
|
||||||
import { ScanController } from './ScanController';
|
|
||||||
|
|
||||||
@JsonController('/cards')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class RunnerCardController {
|
|
||||||
private cardRepository: Repository<RunnerCard>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.cardRepository = getConnectionManager().get().getRepository(RunnerCard);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@Authorized("CARD:GET")
|
|
||||||
@ResponseSchema(ResponseRunnerCard, { isArray: true })
|
|
||||||
@OpenAPI({ description: 'Lists all card.' })
|
|
||||||
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
|
|
||||||
let responseCards: ResponseRunnerCard[] = new Array<ResponseRunnerCard>();
|
|
||||||
let cards: Array<RunnerCard>;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
responseCards.push(new ResponseRunnerCard(card));
|
|
||||||
});
|
|
||||||
return responseCards;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
@Authorized("CARD:GET")
|
|
||||||
@ResponseSchema(ResponseRunnerCard)
|
|
||||||
@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(RunnerCardNotFoundError)
|
|
||||||
@OpenAPI({ description: "Lists all information about the card whose id got provided." })
|
|
||||||
async getOne(@Param('id') id: number) {
|
|
||||||
let card = await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] });
|
|
||||||
if (!card) { throw new RunnerCardNotFoundError(); }
|
|
||||||
return card.toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/bulk')
|
|
||||||
@Authorized("CARD:CREATE")
|
|
||||||
@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." })
|
|
||||||
async postBlancoBulk(@QueryParam("count") count: number, @QueryParam("returnCards") returnCards: boolean = false) {
|
|
||||||
let createPromises = new Array<any>();
|
|
||||||
for (let index = 0; index < count; index++) {
|
|
||||||
createPromises.push(this.cardRepository.save({ runner: null, enabled: true }))
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
response.response = `Created ${count} new blanco cards.`
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@Authorized("CARD:CREATE")
|
|
||||||
@ResponseSchema(ResponseRunnerCard)
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: "Create a new card. <br> You can provide a associated runner by id but you don't have to." })
|
|
||||||
async post(@Body({ validate: true }) createCard: CreateRunnerCard) {
|
|
||||||
let card = await createCard.toEntity();
|
|
||||||
card = await this.cardRepository.save(card);
|
|
||||||
return (await this.cardRepository.findOne({ id: card.id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/:id')
|
|
||||||
@Authorized("CARD:UPDATE")
|
|
||||||
@ResponseSchema(ResponseRunnerCard)
|
|
||||||
@ResponseSchema(RunnerCardNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerCardIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the card whose id you provided. <br> Scans created via this card will still be associated with the old runner. <br> Please remember that ids can't be changed." })
|
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) card: UpdateRunnerCard) {
|
|
||||||
let oldCard = await this.cardRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldCard) {
|
|
||||||
throw new RunnerCardNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldCard.id != card.id) {
|
|
||||||
throw new RunnerCardIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.cardRepository.save(await card.update(oldCard));
|
|
||||||
return (await this.cardRepository.findOne({ id: id }, { relations: ['runner', 'runner.group', 'runner.group.parentGroup'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/:id')
|
|
||||||
@Authorized("CARD:DELETE")
|
|
||||||
@ResponseSchema(ResponseRunnerCard)
|
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
|
||||||
@ResponseSchema(RunnerCardHasScansError, { statusCode: 406 })
|
|
||||||
@OnUndefined(204)
|
|
||||||
@OpenAPI({ description: "Delete the card whose id you provided. <br> If no card with this id exists it will just return 204(no content). <br> If the card still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with by this card - please disable it instead or just remove the runner association)." })
|
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
|
||||||
let card = await this.cardRepository.findOne({ id: id });
|
|
||||||
if (!card) { return null; }
|
|
||||||
|
|
||||||
const cardScans = (await this.cardRepository.findOne({ id: id }, { relations: ["scans"] })).scans;
|
|
||||||
if (cardScans.length != 0 && !force) {
|
|
||||||
throw new RunnerCardHasScansError();
|
|
||||||
}
|
|
||||||
const scanController = new ScanController;
|
|
||||||
for (let scan of cardScans) {
|
|
||||||
await scanController.remove(scan.id, force);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.cardRepository.delete(card);
|
|
||||||
return card.toResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +1,13 @@
|
|||||||
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, 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/CreateRunner';
|
||||||
import { UpdateRunner } from '../models/actions/update/UpdateRunner';
|
import { UpdateRunner } from '../models/actions/UpdateRunner';
|
||||||
import { Runner } from '../models/entities/Runner';
|
import { Runner } from '../models/entities/Runner';
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
|
import { ResponseRunner } from '../models/responses/ResponseRunner';
|
||||||
import { ResponseScan } from '../models/responses/ResponseScan';
|
|
||||||
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
|
|
||||||
import { DonationController } from './DonationController';
|
|
||||||
import { RunnerCardController } from './RunnerCardController';
|
|
||||||
import { ScanController } from './ScanController';
|
|
||||||
|
|
||||||
@JsonController('/runners')
|
@JsonController('/runners')
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||||
@ -30,16 +25,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'] });
|
||||||
|
|
||||||
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));
|
||||||
});
|
});
|
||||||
@ -53,36 +41,11 @@ export class RunnerController {
|
|||||||
@OnUndefined(RunnerNotFoundError)
|
@OnUndefined(RunnerNotFoundError)
|
||||||
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
|
@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
|
||||||
async getOne(@Param('id') id: number) {
|
async getOne(@Param('id') id: number) {
|
||||||
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] })
|
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })
|
||||||
if (!runner) { throw new RunnerNotFoundError(); }
|
if (!runner) { throw new RunnerNotFoundError(); }
|
||||||
return new ResponseRunner(runner);
|
return new ResponseRunner(runner);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/:id/scans')
|
|
||||||
@Authorized(["RUNNER:GET", "SCAN:GET"])
|
|
||||||
@ResponseSchema(ResponseScan, { isArray: true })
|
|
||||||
@ResponseSchema(ResponseTrackScan, { isArray: true })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Lists all scans of the runner whose id got provided. <br> If you only want the valid scans just add the ?onlyValid=true query param.' })
|
|
||||||
async getScans(@Param('id') id: number, onlyValid?: boolean) {
|
|
||||||
let responseScans: ResponseScan[] = new Array<ResponseScan>();
|
|
||||||
let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'scans.track', 'scans.station', 'scans.runner'] })
|
|
||||||
if (!runner) { throw new RunnerNotFoundError(); }
|
|
||||||
|
|
||||||
if (!onlyValid) {
|
|
||||||
for (let scan of runner.scans) {
|
|
||||||
responseScans.push(scan.toResponse());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (let scan of runner.validScans) {
|
|
||||||
responseScans.push(scan.toResponse());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return responseScans;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authorized("RUNNER:CREATE")
|
@Authorized("RUNNER:CREATE")
|
||||||
@ResponseSchema(ResponseRunner)
|
@ResponseSchema(ResponseRunner)
|
||||||
@ -92,13 +55,13 @@ export class RunnerController {
|
|||||||
async post(@Body({ validate: true }) createRunner: CreateRunner) {
|
async post(@Body({ validate: true }) createRunner: CreateRunner) {
|
||||||
let runner;
|
let runner;
|
||||||
try {
|
try {
|
||||||
runner = await createRunner.toEntity();
|
runner = await createRunner.toRunner();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
runner = await this.runnerRepository.save(runner)
|
runner = await this.runnerRepository.save(runner)
|
||||||
return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }));
|
return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('/:id')
|
@Put('/:id')
|
||||||
@ -118,47 +81,25 @@ export class RunnerController {
|
|||||||
throw new RunnerIdsNotMatchingError();
|
throw new RunnerIdsNotMatchingError();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.runnerRepository.save(await runner.update(oldRunner));
|
await this.runnerRepository.save(await runner.updateRunner(oldRunner));
|
||||||
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] }));
|
return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('/:id')
|
@Delete('/:id')
|
||||||
@Authorized("RUNNER:DELETE")
|
@Authorized("RUNNER:DELETE")
|
||||||
@ResponseSchema(ResponseRunner)
|
@ResponseSchema(ResponseRunner)
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||||
@ResponseSchema(RunnerHasDistanceDonationsError, { statusCode: 406 })
|
|
||||||
@OnUndefined(204)
|
@OnUndefined(204)
|
||||||
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> This will also delete all scans and cards associated with the runner. <br> If no runner with this id exists it will just return 204(no content).' })
|
@OpenAPI({ description: 'Delete the runner whose id you provided. <br> If no runner with this id exists it will just return 204(no content).' })
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
||||||
let runner = await this.runnerRepository.findOne({ id: id });
|
let runner = await this.runnerRepository.findOne({ id: id });
|
||||||
if (!runner) { return null; }
|
if (!runner) { return null; }
|
||||||
const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards'] });
|
const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] });
|
||||||
|
|
||||||
if (!runner) {
|
if (!runner) {
|
||||||
throw new RunnerNotFoundError();
|
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);
|
await this.runnerRepository.delete(runner);
|
||||||
return new ResponseRunner(responseRunner);
|
return new ResponseRunner(responseRunner);
|
||||||
}
|
}
|
||||||
|
127
src/controllers/RunnerOrganisationController.ts
Normal file
127
src/controllers/RunnerOrganisationController.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
||||||
|
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
||||||
|
import { getConnectionManager, Repository } from 'typeorm';
|
||||||
|
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
|
||||||
|
import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
|
||||||
|
import { UpdateRunnerOrganisation } from '../models/actions/UpdateRunnerOrganisation';
|
||||||
|
import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
|
||||||
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
|
import { ResponseRunnerOrganisation } from '../models/responses/ResponseRunnerOrganisation';
|
||||||
|
import { RunnerController } from './RunnerController';
|
||||||
|
import { RunnerTeamController } from './RunnerTeamController';
|
||||||
|
|
||||||
|
|
||||||
|
@JsonController('/organisations')
|
||||||
|
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||||
|
export class RunnerOrganisationController {
|
||||||
|
private runnerOrganisationRepository: Repository<RunnerOrganisation>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the repository of this controller's model/entity.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.runnerOrganisationRepository = getConnectionManager().get().getRepository(RunnerOrganisation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@Authorized("ORGANISATION:GET")
|
||||||
|
@ResponseSchema(ResponseRunnerOrganisation, { isArray: true })
|
||||||
|
@OpenAPI({ description: 'Lists all organisations. <br> This includes their address, contact and teams (if existing/associated).' })
|
||||||
|
async getAll() {
|
||||||
|
let responseTeams: ResponseRunnerOrganisation[] = new Array<ResponseRunnerOrganisation>();
|
||||||
|
const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] });
|
||||||
|
runners.forEach(runner => {
|
||||||
|
responseTeams.push(new ResponseRunnerOrganisation(runner));
|
||||||
|
});
|
||||||
|
return responseTeams;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/:id')
|
||||||
|
@Authorized("ORGANISATION:GET")
|
||||||
|
@ResponseSchema(ResponseRunnerOrganisation)
|
||||||
|
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
|
||||||
|
@OnUndefined(RunnerOrganisationNotFoundError)
|
||||||
|
@OpenAPI({ description: 'Lists all information about the organisation whose id got provided.' })
|
||||||
|
async getOne(@Param('id') id: number) {
|
||||||
|
let runnerOrg = await this.runnerOrganisationRepository.findOne({ id: id }, { relations: ['address', 'contact', 'teams'] });
|
||||||
|
if (!runnerOrg) { throw new RunnerOrganisationNotFoundError(); }
|
||||||
|
return new ResponseRunnerOrganisation(runnerOrg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@Authorized("ORGANISATION:CREATE")
|
||||||
|
@ResponseSchema(ResponseRunnerOrganisation)
|
||||||
|
@OpenAPI({ description: 'Create a new organsisation.' })
|
||||||
|
async post(@Body({ validate: true }) createRunnerOrganisation: CreateRunnerOrganisation) {
|
||||||
|
let runnerOrganisation;
|
||||||
|
try {
|
||||||
|
runnerOrganisation = await createRunnerOrganisation.toRunnerOrganisation();
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
runnerOrganisation = await this.runnerOrganisationRepository.save(runnerOrganisation);
|
||||||
|
|
||||||
|
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('/:id')
|
||||||
|
@Authorized("ORGANISATION:UPDATE")
|
||||||
|
@ResponseSchema(ResponseRunnerOrganisation)
|
||||||
|
@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
|
||||||
|
@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
|
||||||
|
@OpenAPI({ description: "Update the organisation whose id you provided. <br> Please remember that ids can't be changed." })
|
||||||
|
async put(@Param('id') id: number, @Body({ validate: true }) updateOrganisation: UpdateRunnerOrganisation) {
|
||||||
|
let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id });
|
||||||
|
|
||||||
|
if (!oldRunnerOrganisation) {
|
||||||
|
throw new RunnerOrganisationNotFoundError();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldRunnerOrganisation.id != updateOrganisation.id) {
|
||||||
|
throw new RunnerOrganisationIdsNotMatchingError();
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.runnerOrganisationRepository.save(await updateOrganisation.updateRunnerOrganisation(oldRunnerOrganisation));
|
||||||
|
|
||||||
|
return new ResponseRunnerOrganisation(await this.runnerOrganisationRepository.findOne(id, { relations: ['address', 'contact', 'teams'] }));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('/:id')
|
||||||
|
@Authorized("ORGANISATION:DELETE")
|
||||||
|
@ResponseSchema(ResponseRunnerOrganisation)
|
||||||
|
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||||
|
@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
|
||||||
|
@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
|
||||||
|
@OnUndefined(204)
|
||||||
|
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organisation still has runners and/or teams associated this will fail. <br> To delete the organisation with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> If no organisation with this id exists it will just return 204(no content).' })
|
||||||
|
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
||||||
|
let organisation = await this.runnerOrganisationRepository.findOne({ id: id });
|
||||||
|
if (!organisation) { return null; }
|
||||||
|
let runnerOrganisation = await this.runnerOrganisationRepository.findOne(organisation, { relations: ['address', 'contact', 'runners', 'teams'] });
|
||||||
|
|
||||||
|
if (!force) {
|
||||||
|
if (runnerOrganisation.teams.length != 0) {
|
||||||
|
throw new RunnerOrganisationHasTeamsError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const teamController = new RunnerTeamController()
|
||||||
|
for (let team of runnerOrganisation.teams) {
|
||||||
|
await teamController.remove(team.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!force) {
|
||||||
|
if (runnerOrganisation.runners.length != 0) {
|
||||||
|
throw new RunnerOrganisationHasRunnersError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const runnerController = new RunnerController()
|
||||||
|
for (let runner of runnerOrganisation.runners) {
|
||||||
|
await runnerController.remove(runner.id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseOrganisation = new ResponseRunnerOrganisation(runnerOrganisation);
|
||||||
|
await this.runnerOrganisationRepository.delete(organisation);
|
||||||
|
return responseOrganisation;
|
||||||
|
}
|
||||||
|
}
|
@ -1,156 +0,0 @@
|
|||||||
import { Authorized, BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { Repository, getConnectionManager } from 'typeorm';
|
|
||||||
import { RunnerOrganizationHasRunnersError, RunnerOrganizationHasTeamsError, RunnerOrganizationIdsNotMatchingError, RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
|
|
||||||
import { CreateRunnerOrganization } from '../models/actions/create/CreateRunnerOrganization';
|
|
||||||
import { UpdateRunnerOrganization } from '../models/actions/update/UpdateRunnerOrganization';
|
|
||||||
import { Runner } from '../models/entities/Runner';
|
|
||||||
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
|
|
||||||
import { ResponseRunnerOrganization } from '../models/responses/ResponseRunnerOrganization';
|
|
||||||
import { RunnerController } from './RunnerController';
|
|
||||||
import { RunnerTeamController } from './RunnerTeamController';
|
|
||||||
|
|
||||||
|
|
||||||
@JsonController('/organizations')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class RunnerOrganizationController {
|
|
||||||
private runnerOrganizationRepository: Repository<RunnerOrganization>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.runnerOrganizationRepository = getConnectionManager().get().getRepository(RunnerOrganization);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@Authorized("ORGANIZATION:GET")
|
|
||||||
@ResponseSchema(ResponseRunnerOrganization, { isArray: true })
|
|
||||||
@OpenAPI({ description: 'Lists all organizations. <br> This includes their address, contact and teams (if existing/associated).' })
|
|
||||||
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
|
|
||||||
let responseOrgs: ResponseRunnerOrganization[] = new Array<ResponseRunnerOrganization>();
|
|
||||||
let orgs: Array<RunnerOrganization>;
|
|
||||||
|
|
||||||
if (page != undefined) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
@Authorized("ORGANIZATION:GET")
|
|
||||||
@ResponseSchema(ResponseRunnerOrganization)
|
|
||||||
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(RunnerOrganizationNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about the organization whose id got provided.' })
|
|
||||||
async getOne(@Param('id') id: number) {
|
|
||||||
let runnerOrg = await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['contact', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.scans.track', 'runners', 'runners.scans', 'runners.scans.track'] });
|
|
||||||
if (!runnerOrg) { throw new RunnerOrganizationNotFoundError(); }
|
|
||||||
return new ResponseRunnerOrganization(runnerOrg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id/runners')
|
|
||||||
@Authorized(["RUNNER:GET", "SCAN:GET"])
|
|
||||||
@ResponseSchema(ResponseRunner, { isArray: true })
|
|
||||||
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Lists all runners from this org and it\'s teams (if you don\'t provide the ?onlyDirect=true param). <br> This includes the runner\'s group and distance ran.' })
|
|
||||||
async getRunners(@Param('id') id: number, @QueryParam('onlyDirect') onlyDirect: boolean) {
|
|
||||||
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
|
|
||||||
let runners: Runner[];
|
|
||||||
if (!onlyDirect) { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.group', 'teams.runners.group.parentGroup', 'teams.runners.scans', 'teams.runners.scans.track'] })).allRunners; }
|
|
||||||
else { runners = (await this.runnerOrganizationRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners; }
|
|
||||||
runners.forEach(runner => {
|
|
||||||
responseRunners.push(new ResponseRunner(runner));
|
|
||||||
});
|
|
||||||
return responseRunners;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@Authorized("ORGANIZATION:CREATE")
|
|
||||||
@ResponseSchema(ResponseRunnerOrganization)
|
|
||||||
@OpenAPI({ description: 'Create a new organsisation.' })
|
|
||||||
async post(@Body({ validate: true }) createRunnerOrganization: CreateRunnerOrganization) {
|
|
||||||
let runnerOrganization;
|
|
||||||
try {
|
|
||||||
runnerOrganization = await createRunnerOrganization.toEntity();
|
|
||||||
} catch (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
runnerOrganization = await this.runnerOrganizationRepository.save(runnerOrganization);
|
|
||||||
|
|
||||||
return new ResponseRunnerOrganization(await this.runnerOrganizationRepository.findOne(runnerOrganization, { relations: ['contact', 'teams'] }));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/:id')
|
|
||||||
@Authorized("ORGANIZATION:UPDATE")
|
|
||||||
@ResponseSchema(ResponseRunnerOrganization)
|
|
||||||
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerOrganizationIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the organization whose id you provided. <br> Please remember that ids can't be changed." })
|
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) updateOrganization: UpdateRunnerOrganization) {
|
|
||||||
let oldRunnerOrganization = await this.runnerOrganizationRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldRunnerOrganization) {
|
|
||||||
throw new RunnerOrganizationNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldRunnerOrganization.id != updateOrganization.id) {
|
|
||||||
throw new RunnerOrganizationIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.runnerOrganizationRepository.save(await updateOrganization.update(oldRunnerOrganization));
|
|
||||||
|
|
||||||
return new ResponseRunnerOrganization(await this.runnerOrganizationRepository.findOne(id, { relations: ['contact', 'teams'] }));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/:id')
|
|
||||||
@Authorized("ORGANIZATION:DELETE")
|
|
||||||
@ResponseSchema(ResponseRunnerOrganization)
|
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
|
||||||
@ResponseSchema(RunnerOrganizationHasTeamsError, { statusCode: 406 })
|
|
||||||
@ResponseSchema(RunnerOrganizationHasRunnersError, { statusCode: 406 })
|
|
||||||
@OnUndefined(204)
|
|
||||||
@OpenAPI({ description: 'Delete the organsisation whose id you provided. <br> If the organization still has runners and/or teams associated this will fail. <br> To delete the organization with all associated runners and teams set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact. <br> If no organization with this id exists it will just return 204(no content).' })
|
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
|
||||||
if (id == 1) {
|
|
||||||
throw new BadRequestError("You can't delete the citizen runner org.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let organization = await this.runnerOrganizationRepository.findOne({ id: id });
|
|
||||||
if (!organization) { return null; }
|
|
||||||
let runnerOrganization = await this.runnerOrganizationRepository.findOne(organization, { relations: ['contact', 'runners', 'teams'] });
|
|
||||||
|
|
||||||
if (!force) {
|
|
||||||
if (runnerOrganization.teams.length != 0) {
|
|
||||||
throw new RunnerOrganizationHasTeamsError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const teamController = new RunnerTeamController()
|
|
||||||
for (let team of runnerOrganization.teams) {
|
|
||||||
await teamController.remove(team.id, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!force) {
|
|
||||||
if (runnerOrganization.runners.length != 0) {
|
|
||||||
throw new RunnerOrganizationHasRunnersError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const runnerController = new RunnerController()
|
|
||||||
for (let runner of runnerOrganization.runners) {
|
|
||||||
await runnerController.remove(runner.id, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseOrganization = new ResponseRunnerOrganization(runnerOrganization);
|
|
||||||
await this.runnerOrganizationRepository.delete(organization);
|
|
||||||
return responseOrganization;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,244 +0,0 @@
|
|||||||
import { Request } from "express";
|
|
||||||
import * as jwt from "jsonwebtoken";
|
|
||||||
import { BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { getConnectionManager, Repository } from 'typeorm';
|
|
||||||
import { config } from '../config';
|
|
||||||
import { InvalidCredentialsError, JwtNotProvidedError } from '../errors/AuthError';
|
|
||||||
import { MailSendingError } from '../errors/MailErrors';
|
|
||||||
import { RunnerEmailNeededError, RunnerHasDistanceDonationsError, RunnerNotFoundError, RunnerSelfserviceTimeoutError } from '../errors/RunnerErrors';
|
|
||||||
import { RunnerOrganizationNotFoundError } from '../errors/RunnerOrganizationErrors';
|
|
||||||
import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
|
|
||||||
import { JwtCreator } from '../jwtcreator';
|
|
||||||
import { Mailer } from '../mailer';
|
|
||||||
import ScanAuth from '../middlewares/ScanAuth';
|
|
||||||
import { CreateSelfServiceCitizenRunner } from '../models/actions/create/CreateSelfServiceCitizenRunner';
|
|
||||||
import { CreateSelfServiceRunner } from '../models/actions/create/CreateSelfServiceRunner';
|
|
||||||
import { Runner } from '../models/entities/Runner';
|
|
||||||
import { RunnerGroup } from '../models/entities/RunnerGroup';
|
|
||||||
import { RunnerOrganization } from '../models/entities/RunnerOrganization';
|
|
||||||
import { ScanStation } from '../models/entities/ScanStation';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
|
|
||||||
import { ResponseSelfServiceOrganisation } from '../models/responses/ResponseSelfServiceOrganisation';
|
|
||||||
import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
|
|
||||||
import { ResponseSelfServiceScan } from '../models/responses/ResponseSelfServiceScan';
|
|
||||||
import { DonationController } from './DonationController';
|
|
||||||
import { RunnerCardController } from './RunnerCardController';
|
|
||||||
import { ScanController } from './ScanController';
|
|
||||||
|
|
||||||
@JsonController()
|
|
||||||
export class RunnerSelfServiceController {
|
|
||||||
private runnerRepository: Repository<Runner>;
|
|
||||||
private orgRepository: Repository<RunnerOrganization>;
|
|
||||||
private stationRepository: Repository<ScanStation>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.runnerRepository = getConnectionManager().get().getRepository(Runner);
|
|
||||||
this.orgRepository = getConnectionManager().get().getRepository(RunnerOrganization);
|
|
||||||
this.stationRepository = getConnectionManager().get().getRepository(ScanStation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/runners/me/:jwt')
|
|
||||||
@ResponseSchema(ResponseSelfServiceRunner)
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(RunnerNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about yourself. <br> Please provide your runner jwt(that code we gave you during registration) for auth. <br> If you lost your jwt/personalized link please use the forgot endpoint.' })
|
|
||||||
async get(@Param('jwt') token: string) {
|
|
||||||
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')
|
|
||||||
@ResponseSchema(ResponseSelfServiceScan, { isArray: true })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(RunnerNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all your (runner) scans. <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 getScans(@Param('jwt') token: string) {
|
|
||||||
const scans = (await this.getRunner(token)).scans;
|
|
||||||
let responseScans = new Array<ResponseSelfServiceScan>()
|
|
||||||
for (let scan of scans) {
|
|
||||||
responseScans.push(new ResponseSelfServiceScan(scan));
|
|
||||||
}
|
|
||||||
return responseScans;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/stations/me')
|
|
||||||
@UseBefore(ScanAuth)
|
|
||||||
@ResponseSchema(ResponseScanStation)
|
|
||||||
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(ScanStationNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists basic information about the station whose token got provided. <br> This includes it\'s associated track.', security: [{ "StationApiToken": [] }] })
|
|
||||||
async getStationMe(@Req() req: Request) {
|
|
||||||
let scan = await this.stationRepository.findOne({ id: parseInt(req.headers["station_id"].toString()) }, { relations: ['track'] })
|
|
||||||
if (!scan) { throw new ScanStationNotFoundError(); }
|
|
||||||
return scan.toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/runners/login')
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@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).' })
|
|
||||||
async requestNewToken(@QueryParam('mail') mail: string, @QueryParam("locale") locale: string = "en") {
|
|
||||||
if (!mail) {
|
|
||||||
throw new RunnerNotFoundError();
|
|
||||||
}
|
|
||||||
const runner = await this.runnerRepository.findOne({ email: mail });
|
|
||||||
if (!runner) { throw new RunnerNotFoundError(); }
|
|
||||||
|
|
||||||
if (runner.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 60 * 15)) { throw new RunnerSelfserviceTimeoutError(); }
|
|
||||||
const token = JwtCreator.createSelfService(runner);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Mailer.sendSelfserviceForgottenMail(runner.email, token, locale)
|
|
||||||
} catch (error) {
|
|
||||||
throw new MailSendingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
runner.resetRequestedTimestamp = Math.floor(Date.now() / 1000);
|
|
||||||
await this.runnerRepository.save(runner);
|
|
||||||
|
|
||||||
return { token };
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/runners/register')
|
|
||||||
@ResponseSchema(ResponseSelfServiceRunner)
|
|
||||||
@ResponseSchema(RunnerEmailNeededError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: 'Create a new selfservice runner in the citizen org. <br> This endpoint shoud be used to allow "everyday citizen" to register themselves. <br> You have to provide a mail address, b/c the future we\'ll implement email verification.' })
|
|
||||||
async registerRunner(@Body({ validate: true }) createRunner: CreateSelfServiceCitizenRunner, @QueryParam("locale") locale: string = "en") {
|
|
||||||
let runner = await createRunner.toEntity();
|
|
||||||
if (await this.getRunnerExistsByMail(runner.email)) {
|
|
||||||
throw new BadRequestError("E-Mail already registered")
|
|
||||||
}
|
|
||||||
runner = await this.runnerRepository.save(runner);
|
|
||||||
|
|
||||||
let response = new ResponseSelfServiceRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }));
|
|
||||||
response.token = JwtCreator.createSelfService(runner);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale)
|
|
||||||
} catch (error) {
|
|
||||||
throw new MailSendingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('/runners/register/:token')
|
|
||||||
@ResponseSchema(ResponseSelfServiceRunner)
|
|
||||||
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Create a new selfservice runner in a provided org. <br> The orgs get provided and authorized via api tokens that can be optained via the /organizations endpoint.' })
|
|
||||||
async registerOrganizationRunner(@Param('token') token: string, @Body({ validate: true }) createRunner: CreateSelfServiceRunner, @QueryParam("locale") locale: string = "en") {
|
|
||||||
const org = await this.getOrgansisation(token);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Mailer.sendSelfserviceWelcomeMail(runner.email, response.token, locale)
|
|
||||||
} catch (error) {
|
|
||||||
throw new MailSendingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/organizations/selfservice/:token')
|
|
||||||
@ResponseSchema(ResponseSelfServiceOrganisation, { isArray: false })
|
|
||||||
@ResponseSchema(RunnerOrganizationNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Get the basic info and teams for a org.' })
|
|
||||||
async getSelfserviceOrg(@Param('token') token: string) {
|
|
||||||
const orgid = (await this.getOrgansisation(token)).id;
|
|
||||||
const org = await this.orgRepository.findOne({ id: orgid }, { relations: ['teams'] })
|
|
||||||
|
|
||||||
return new ResponseSelfServiceOrganisation(<RunnerOrganization>org);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get's a runner by a provided jwt token.
|
|
||||||
* @param token The runner jwt provided by the runner to identitfy themselves.
|
|
||||||
*/
|
|
||||||
private async getRunner(token: string): Promise<Runner> {
|
|
||||||
if (token == "") { throw new JwtNotProvidedError(); }
|
|
||||||
let jwtPayload = undefined
|
|
||||||
try {
|
|
||||||
jwtPayload = <any>jwt.verify(token, config.jwt_secret);
|
|
||||||
} catch (error) {
|
|
||||||
throw new InvalidCredentialsError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const runner = await this.runnerRepository.findOne({ id: jwtPayload["id"] }, { relations: ['scans', 'group', 'group.parentGroup', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] });
|
|
||||||
if (!runner) { throw new RunnerNotFoundError() }
|
|
||||||
return runner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get's a runner org by a provided registration api key.
|
|
||||||
* @param token The organization's registration api token.
|
|
||||||
*/
|
|
||||||
private async getOrgansisation(token: string): Promise<RunnerGroup> {
|
|
||||||
token = Buffer.from(token, 'base64').toString('utf8');
|
|
||||||
|
|
||||||
const organization = await this.orgRepository.findOne({ key: token });
|
|
||||||
if (!organization) { throw new RunnerOrganizationNotFoundError; }
|
|
||||||
|
|
||||||
return organization;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,11 @@
|
|||||||
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/CreateRunnerTeam';
|
||||||
import { UpdateRunnerTeam } from '../models/actions/update/UpdateRunnerTeam';
|
import { UpdateRunnerTeam } from '../models/actions/UpdateRunnerTeam';
|
||||||
import { RunnerTeam } from '../models/entities/RunnerTeam';
|
import { RunnerTeam } from '../models/entities/RunnerTeam';
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
|
|
||||||
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
|
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
|
||||||
import { RunnerController } from './RunnerController';
|
import { RunnerController } from './RunnerController';
|
||||||
|
|
||||||
@ -26,19 +25,12 @@ export class RunnerTeamController {
|
|||||||
@Get()
|
@Get()
|
||||||
@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 organisation 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,25 +42,11 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/:id/runners')
|
|
||||||
@Authorized(["RUNNER:GET", "SCAN:GET"])
|
|
||||||
@ResponseSchema(ResponseRunner, { isArray: true })
|
|
||||||
@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Lists all runners from this team. <br> This includes the runner\'s group and distance ran.' })
|
|
||||||
async getRunners(@Param('id') id: number) {
|
|
||||||
let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
|
|
||||||
const runners = (await this.runnerTeamRepository.findOne({ id: id }, { relations: ['runners', 'runners.group', 'runners.group.parentGroup', 'runners.scans', 'runners.scans.track'] })).runners;
|
|
||||||
runners.forEach(runner => {
|
|
||||||
responseRunners.push(new ResponseRunner(runner));
|
|
||||||
});
|
|
||||||
return responseRunners;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authorized("TEAM:CREATE")
|
@Authorized("TEAM:CREATE")
|
||||||
@ResponseSchema(ResponseRunnerTeam)
|
@ResponseSchema(ResponseRunnerTeam)
|
||||||
@ -76,7 +54,7 @@ export class RunnerTeamController {
|
|||||||
async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
|
async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
|
||||||
let runnerTeam;
|
let runnerTeam;
|
||||||
try {
|
try {
|
||||||
runnerTeam = await createRunnerTeam.toEntity();
|
runnerTeam = await createRunnerTeam.toRunnerTeam();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
@ -104,7 +82,7 @@ export class RunnerTeamController {
|
|||||||
throw new RunnerTeamIdsNotMatchingError();
|
throw new RunnerTeamIdsNotMatchingError();
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.runnerTeamRepository.save(await runnerTeam.update(oldRunnerTeam));
|
await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam));
|
||||||
|
|
||||||
return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
|
return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
|
||||||
}
|
}
|
||||||
@ -115,7 +93,7 @@ export class RunnerTeamController {
|
|||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||||
@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
|
@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
|
||||||
@OnUndefined(204)
|
@OnUndefined(204)
|
||||||
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> This won\'t delete the associated contact.<br> If no team with this id exists it will just return 204(no content).' })
|
@OpenAPI({ description: 'Delete the team whose id you provided. <br> If the team still has runners associated this will fail. <br> To delete the team with all associated runners set the force QueryParam to true (cascading deletion might take a while). <br> If no team 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) {
|
||||||
let team = await this.runnerTeamRepository.findOne({ id: id });
|
let team = await this.runnerTeamRepository.findOne({ id: id });
|
||||||
if (!team) { return null; }
|
if (!team) { return null; }
|
||||||
|
@ -1,151 +0,0 @@
|
|||||||
import { Request } from "express";
|
|
||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { Repository, getConnectionManager } from 'typeorm';
|
|
||||||
import { RunnerNotFoundError } from '../errors/RunnerErrors';
|
|
||||||
import { ScanIdsNotMatchingError, ScanNotFoundError } from '../errors/ScanErrors';
|
|
||||||
import { ScanStationNotFoundError } from '../errors/ScanStationErrors';
|
|
||||||
import ScanAuth from '../middlewares/ScanAuth';
|
|
||||||
import { CreateScan } from '../models/actions/create/CreateScan';
|
|
||||||
import { CreateTrackScan } from '../models/actions/create/CreateTrackScan';
|
|
||||||
import { UpdateScan } from '../models/actions/update/UpdateScan';
|
|
||||||
import { UpdateTrackScan } from '../models/actions/update/UpdateTrackScan';
|
|
||||||
import { Scan } from '../models/entities/Scan';
|
|
||||||
import { TrackScan } from '../models/entities/TrackScan';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
import { ResponseScan } from '../models/responses/ResponseScan';
|
|
||||||
import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
|
|
||||||
@JsonController('/scans')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class ScanController {
|
|
||||||
private scanRepository: Repository<Scan>;
|
|
||||||
private trackScanRepository: Repository<TrackScan>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.scanRepository = getConnectionManager().get().getRepository(Scan);
|
|
||||||
this.trackScanRepository = getConnectionManager().get().getRepository(TrackScan);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@Authorized("SCAN:GET")
|
|
||||||
@ResponseSchema(ResponseScan, { isArray: true })
|
|
||||||
@ResponseSchema(ResponseTrackScan, { isArray: true })
|
|
||||||
@OpenAPI({ description: 'Lists all scans (normal or track) from all runners. <br> This includes the scan\'s runner\'s distance ran.' })
|
|
||||||
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
|
|
||||||
let responseScans: ResponseScan[] = new Array<ResponseScan>();
|
|
||||||
let scans: Array<Scan>;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
responseScans.push(scan.toResponse());
|
|
||||||
});
|
|
||||||
return responseScans;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
@Authorized("SCAN:GET")
|
|
||||||
@ResponseSchema(ResponseScan)
|
|
||||||
@ResponseSchema(ResponseTrackScan)
|
|
||||||
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(ScanNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about the scan whose id got provided. This includes the scan\'s runner\'s distance ran.' })
|
|
||||||
async getOne(@Param('id') id: number) {
|
|
||||||
let scan = await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.group', 'card', 'station'] })
|
|
||||||
if (!scan) { throw new ScanNotFoundError(); }
|
|
||||||
return scan.toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@UseBefore(ScanAuth)
|
|
||||||
@ResponseSchema(ResponseScan)
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Create a new scan (not track scan - use /scans/trackscans instead). <br> Please rmemember to provide the scan\'s runner\'s id and distance.', security: [{ "StationApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
async post(@Body({ validate: true }) createScan: CreateScan) {
|
|
||||||
let scan = await createScan.toEntity();
|
|
||||||
scan = await this.scanRepository.save(scan);
|
|
||||||
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post("/trackscans")
|
|
||||||
@UseBefore(ScanAuth)
|
|
||||||
@ResponseSchema(ResponseTrackScan)
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Create a new track scan (for "normal" scans use /scans instead). <br> Please remember that to provide the scan\'s card\'s station\'s id.', security: [{ "StationApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
async postTrackScans(@Body({ validate: true }) createScan: CreateTrackScan, @Req() req: Request) {
|
|
||||||
const station_id = req.headers["station_id"];
|
|
||||||
if (station_id) {
|
|
||||||
createScan.station = parseInt(station_id.toString());
|
|
||||||
}
|
|
||||||
let scan = await createScan.toEntity();
|
|
||||||
scan = await this.trackScanRepository.save(scan);
|
|
||||||
return (await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/:id')
|
|
||||||
@Authorized("SCAN:UPDATE")
|
|
||||||
@ResponseSchema(ResponseScan)
|
|
||||||
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the scan (not track scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that ids can't be changed and distances must be positive." })
|
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) scan: UpdateScan) {
|
|
||||||
let oldScan = await this.scanRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldScan) {
|
|
||||||
throw new ScanNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldScan.id != scan.id) {
|
|
||||||
throw new ScanIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.scanRepository.save(await scan.update(oldScan));
|
|
||||||
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/trackscans/:id')
|
|
||||||
@Authorized("SCAN:UPDATE")
|
|
||||||
@ResponseSchema(ResponseTrackScan)
|
|
||||||
@ResponseSchema(ScanNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(ScanIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: 'Update the track scan (not "normal" scan use /scans/trackscans/:id instead) whose id you provided. <br> Please remember that only the validity, runner and track can be changed.' })
|
|
||||||
async putTrackScan(@Param('id') id: number, @Body({ validate: true }) scan: UpdateTrackScan) {
|
|
||||||
let oldScan = await this.trackScanRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldScan) {
|
|
||||||
throw new ScanNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldScan.id != scan.id) {
|
|
||||||
throw new ScanIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.trackScanRepository.save(await scan.update(oldScan));
|
|
||||||
return (await this.scanRepository.findOne({ id: id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/:id')
|
|
||||||
@Authorized("SCAN:DELETE")
|
|
||||||
@ResponseSchema(ResponseScan)
|
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
|
||||||
@OnUndefined(204)
|
|
||||||
@OpenAPI({ description: 'Delete the scan whose id you provided. <br> If no scan with this id exists it will just return 204(no content).' })
|
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
|
||||||
let scan = await this.scanRepository.findOne({ id: id });
|
|
||||||
if (!scan) { return null; }
|
|
||||||
const responseScan = await this.scanRepository.findOne({ id: scan.id }, { relations: ['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track', 'card', 'station'] });
|
|
||||||
|
|
||||||
await this.scanRepository.delete(scan);
|
|
||||||
return responseScan.toResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
|
||||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
|
|
||||||
import { Repository, getConnectionManager } from 'typeorm';
|
|
||||||
import { ScanStationHasScansError, ScanStationIdsNotMatchingError, ScanStationNotFoundError } from '../errors/ScanStationErrors';
|
|
||||||
import { TrackNotFoundError } from '../errors/TrackErrors';
|
|
||||||
import { CreateScanStation } from '../models/actions/create/CreateScanStation';
|
|
||||||
import { UpdateScanStation } from '../models/actions/update/UpdateScanStation';
|
|
||||||
import { ScanStation } from '../models/entities/ScanStation';
|
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
|
||||||
import { ResponseScanStation } from '../models/responses/ResponseScanStation';
|
|
||||||
import { ScanController } from './ScanController';
|
|
||||||
|
|
||||||
@JsonController('/stations')
|
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
|
||||||
export class ScanStationController {
|
|
||||||
private stationRepository: Repository<ScanStation>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the repository of this controller's model/entity.
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
this.stationRepository = getConnectionManager().get().getRepository(ScanStation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get()
|
|
||||||
@Authorized("STATION:GET")
|
|
||||||
@ResponseSchema(ResponseScanStation, { isArray: true })
|
|
||||||
@OpenAPI({ description: 'Lists all stations. <br> This includes their associated tracks.' })
|
|
||||||
async getAll(@QueryParam("page", { required: false }) page: number, @QueryParam("page_size", { required: false }) page_size: number = 100) {
|
|
||||||
let responseStations: ResponseScanStation[] = new Array<ResponseScanStation>();
|
|
||||||
let stations: Array<ScanStation>;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
responseStations.push(station.toResponse());
|
|
||||||
});
|
|
||||||
return responseStations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id')
|
|
||||||
@Authorized("STATION:GET")
|
|
||||||
@ResponseSchema(ResponseScanStation)
|
|
||||||
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(ScanStationNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all information about the station whose id got provided. <br> This includes it\'s associated track.' })
|
|
||||||
async getOne(@Param('id') id: number) {
|
|
||||||
let scan = await this.stationRepository.findOne({ id: id }, { relations: ['track'] })
|
|
||||||
if (!scan) { throw new ScanStationNotFoundError(); }
|
|
||||||
return scan.toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
|
||||||
@Authorized("STATION:CREATE")
|
|
||||||
@ResponseSchema(ResponseScanStation)
|
|
||||||
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
|
|
||||||
@OpenAPI({ description: 'Create a new station. <br> Please remeber to provide the station\'s track\'s id. <br> Please also remember that the station key is only visibe on creation.' })
|
|
||||||
async post(@Body({ validate: true }) createStation: CreateScanStation) {
|
|
||||||
let newStation = await createStation.toEntity();
|
|
||||||
const station = await this.stationRepository.save(newStation);
|
|
||||||
let responseStation = (await this.stationRepository.findOne({ id: station.id }, { relations: ['track'] })).toResponse();
|
|
||||||
responseStation.key = newStation.cleartextkey;
|
|
||||||
return responseStation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Put('/:id')
|
|
||||||
@Authorized("STATION:UPDATE")
|
|
||||||
@ResponseSchema(ResponseScanStation)
|
|
||||||
@ResponseSchema(ScanStationNotFoundError, { statusCode: 404 })
|
|
||||||
@ResponseSchema(ScanStationIdsNotMatchingError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the station whose id you provided. <br> Please remember that only the description and enabled state can be changed." })
|
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) station: UpdateScanStation) {
|
|
||||||
let oldStation = await this.stationRepository.findOne({ id: id });
|
|
||||||
|
|
||||||
if (!oldStation) {
|
|
||||||
throw new ScanStationNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldStation.id != station.id) {
|
|
||||||
throw new ScanStationIdsNotMatchingError();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.stationRepository.save(await station.update(oldStation));
|
|
||||||
return (await this.stationRepository.findOne({ id: id }, { relations: ['track'] })).toResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('/:id')
|
|
||||||
@Authorized("STATION:DELETE")
|
|
||||||
@ResponseSchema(ResponseScanStation)
|
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
|
||||||
@ResponseSchema(ScanStationHasScansError, { statusCode: 406 })
|
|
||||||
@OnUndefined(204)
|
|
||||||
@OpenAPI({ description: 'Delete the station whose id you provided. <br> If no station with this id exists it will just return 204(no content). <br> If the station still has scans associated you have to provide the force=true query param (warning: this deletes all scans associated with/created by this station - please disable it instead).' })
|
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
|
||||||
let station = await this.stationRepository.findOne({ id: id });
|
|
||||||
if (!station) { return null; }
|
|
||||||
|
|
||||||
const stationScans = (await this.stationRepository.findOne({ id: station.id }, { relations: ["scans"] })).scans;
|
|
||||||
if (stationScans.length != 0 && !force) {
|
|
||||||
throw new ScanStationHasScansError();
|
|
||||||
}
|
|
||||||
const scanController = new ScanController;
|
|
||||||
for (let scan of stationScans) {
|
|
||||||
await scanController.remove(scan.id, force);
|
|
||||||
}
|
|
||||||
|
|
||||||
const responseStation = await this.stationRepository.findOne({ id: station.id }, { relations: ["track"] });
|
|
||||||
await this.stationRepository.delete(station);
|
|
||||||
return responseStation.toResponse();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,9 @@
|
|||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam } from 'routing-controllers';
|
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post } 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/CreateStatsClient';
|
||||||
import { StatsClient } from '../models/entities/StatsClient';
|
import { StatsClient } from '../models/entities/StatsClient';
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
import { ResponseStatsClient } from '../models/responses/ResponseStatsClient';
|
import { ResponseStatsClient } from '../models/responses/ResponseStatsClient';
|
||||||
@ -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));
|
||||||
});
|
});
|
||||||
@ -60,7 +53,7 @@ export class StatsClientController {
|
|||||||
@Body({ validate: true })
|
@Body({ validate: true })
|
||||||
client: CreateStatsClient
|
client: CreateStatsClient
|
||||||
) {
|
) {
|
||||||
let newClient = await this.clientRepository.save(await client.toEntity());
|
let newClient = await this.clientRepository.save(await client.toStatsClient());
|
||||||
let responseClient = new ResponseStatsClient(newClient);
|
let responseClient = new ResponseStatsClient(newClient);
|
||||||
responseClient.key = newClient.cleartextkey;
|
responseClient.key = newClient.cleartextkey;
|
||||||
return responseClient;
|
return responseClient;
|
||||||
@ -72,7 +65,7 @@ export class StatsClientController {
|
|||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||||
@OnUndefined(204)
|
@OnUndefined(204)
|
||||||
@OpenAPI({ description: "Delete the stats client whose id you provided. <br> If no client with this id exists it will just return 204(no content)." })
|
@OpenAPI({ description: "Delete the stats client whose id you provided. <br> If no client with this id exists it will just return 204(no content)." })
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
async remove(@Param("id") id: number) {
|
||||||
let client = await this.clientRepository.findOne({ id: id });
|
let client = await this.clientRepository.findOne({ id: id });
|
||||||
if (!client) { return null; }
|
if (!client) { return null; }
|
||||||
|
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
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 { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
|
||||||
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/ResponseStatsOrganisation';
|
||||||
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
|
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
|
||||||
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
|
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
|
||||||
|
|
||||||
@ -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(RunnerOrganisation).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));
|
||||||
@ -148,16 +94,13 @@ export class StatsController {
|
|||||||
return responseTeams;
|
return responseTeams;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("/organizations/distance")
|
@Get("/organisations/distance")
|
||||||
@UseBefore(StatsAuth)
|
@UseBefore(StatsAuth)
|
||||||
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
|
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
|
||||||
@OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
@OpenAPI({ description: "Returns the top ten organisations 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(RunnerOrganisation).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));
|
||||||
@ -165,16 +108,13 @@ export class StatsController {
|
|||||||
return responseOrgs;
|
return responseOrgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("/organizations/donations")
|
@Get("/organisations/donations")
|
||||||
@UseBefore(StatsAuth)
|
@UseBefore(StatsAuth)
|
||||||
@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 organisations 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(RunnerOrganisation).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));
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Get, JsonController } from 'routing-controllers';
|
import { Get, JsonController } from 'routing-controllers';
|
||||||
import { OpenAPI } from 'routing-controllers-openapi';
|
import { OpenAPI } from 'routing-controllers-openapi';
|
||||||
import { getConnection } from 'typeorm';
|
import { getConnection } from 'typeorm';
|
||||||
import { config } from '../config';
|
|
||||||
|
|
||||||
@JsonController()
|
@JsonController('/status')
|
||||||
export class StatusController {
|
export class StatusController {
|
||||||
|
|
||||||
@Get('/status')
|
@Get()
|
||||||
@OpenAPI({ description: "A very basic status/health endpoint that just checks if the database connection is available. <br> The available information depth will be expanded later." })
|
@OpenAPI({ description: "A very basic status/health endpoint that just checks if the database connection is available. <br> The available information depth will be expanded later." })
|
||||||
get() {
|
get() {
|
||||||
let connection;
|
let connection;
|
||||||
@ -20,12 +19,4 @@ export class StatusController {
|
|||||||
"database connection": "✔"
|
"database connection": "✔"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/version')
|
|
||||||
@OpenAPI({ description: "A very basic endpoint that just returns the curent package version." })
|
|
||||||
getVersion() {
|
|
||||||
return {
|
|
||||||
"version": config.version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,13 +1,12 @@
|
|||||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
|
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } 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 { EntityFromBody } from 'typeorm-routing-controllers-extensions';
|
||||||
import { CreateTrack } from '../models/actions/create/CreateTrack';
|
import { TrackIdsNotMatchingError, TrackNotFoundError } from "../errors/TrackErrors";
|
||||||
import { UpdateTrack } from '../models/actions/update/UpdateTrack';
|
import { CreateTrack } from '../models/actions/CreateTrack';
|
||||||
import { Track } from '../models/entities/Track';
|
import { Track } from '../models/entities/Track';
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
import { ResponseTrack } from '../models/responses/ResponseTrack';
|
import { ResponseTrack } from '../models/responses/ResponseTrack';
|
||||||
import { ScanStationController } from './ScanStationController';
|
|
||||||
|
|
||||||
@JsonController('/tracks')
|
@JsonController('/tracks')
|
||||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
|
||||||
@ -25,17 +24,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));
|
||||||
});
|
});
|
||||||
@ -57,13 +48,12 @@ export class TrackController {
|
|||||||
@Post()
|
@Post()
|
||||||
@Authorized("TRACK:CREATE")
|
@Authorized("TRACK:CREATE")
|
||||||
@ResponseSchema(ResponseTrack)
|
@ResponseSchema(ResponseTrack)
|
||||||
@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Create a new track. <br> Please remember that the track\'s distance must be greater than 0." })
|
@OpenAPI({ description: "Create a new track. <br> Please remember that the track\'s distance must be greater than 0." })
|
||||||
async post(
|
async post(
|
||||||
@Body({ validate: true })
|
@Body({ validate: true })
|
||||||
track: CreateTrack
|
track: CreateTrack
|
||||||
) {
|
) {
|
||||||
return new ResponseTrack(await this.trackRepository.save(await track.toEntity()));
|
return new ResponseTrack(await this.trackRepository.save(track.toTrack()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('/:id')
|
@Put('/:id')
|
||||||
@ -71,21 +61,20 @@ export class TrackController {
|
|||||||
@ResponseSchema(ResponseTrack)
|
@ResponseSchema(ResponseTrack)
|
||||||
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
|
@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
|
||||||
@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 })
|
@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 })
|
||||||
@ResponseSchema(TrackLapTimeCantBeNegativeError, { statusCode: 406 })
|
|
||||||
@OpenAPI({ description: "Update the track whose id you provided. <br> Please remember that ids can't be changed." })
|
@OpenAPI({ description: "Update the track whose id you provided. <br> Please remember that ids can't be changed." })
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) updateTrack: UpdateTrack) {
|
async put(@Param('id') id: number, @EntityFromBody() track: Track) {
|
||||||
let oldTrack = await this.trackRepository.findOne({ id: id });
|
let oldTrack = await this.trackRepository.findOne({ id: id });
|
||||||
|
|
||||||
if (!oldTrack) {
|
if (!oldTrack) {
|
||||||
throw new TrackNotFoundError();
|
throw new TrackNotFoundError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldTrack.id != updateTrack.id) {
|
if (oldTrack.id != track.id) {
|
||||||
throw new TrackIdsNotMatchingError();
|
throw new TrackIdsNotMatchingError();
|
||||||
}
|
}
|
||||||
await this.trackRepository.save(await updateTrack.update(oldTrack));
|
|
||||||
|
|
||||||
return new ResponseTrack(await this.trackRepository.findOne({ id: id }));
|
await this.trackRepository.save(track);
|
||||||
|
return new ResponseTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('/:id')
|
@Delete('/:id')
|
||||||
@ -94,19 +83,10 @@ export class TrackController {
|
|||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||||
@OnUndefined(204)
|
@OnUndefined(204)
|
||||||
@OpenAPI({ description: "Delete the track whose id you provided. <br> If no track with this id exists it will just return 204(no content)." })
|
@OpenAPI({ description: "Delete the track whose id you provided. <br> If no track with this id exists it will just return 204(no content)." })
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
async remove(@Param("id") id: number) {
|
||||||
let track = await this.trackRepository.findOne({ id: id });
|
let track = await this.trackRepository.findOne({ id: id });
|
||||||
if (!track) { return null; }
|
if (!track) { return null; }
|
||||||
|
|
||||||
const trackStations = (await this.trackRepository.findOne({ id: id }, { relations: ["stations"] })).stations;
|
|
||||||
if (trackStations.length != 0 && !force) {
|
|
||||||
throw new TrackHasScanStationsError();
|
|
||||||
}
|
|
||||||
const stationController = new ScanStationController;
|
|
||||||
for (let station of trackStations) {
|
|
||||||
await stationController.remove(station.id, force);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.trackRepository.delete(track);
|
await this.trackRepository.delete(track);
|
||||||
return new ResponseTrack(track);
|
return new ResponseTrack(track);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
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 { UserIdsNotMatchingError, 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/CreateUser';
|
||||||
import { UpdateUser } from '../models/actions/update/UpdateUser';
|
import { UpdateUser } from '../models/actions/UpdateUser';
|
||||||
import { User } from '../models/entities/User';
|
import { User } from '../models/entities/User';
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
import { ResponseUser } from '../models/responses/ResponseUser';
|
import { ResponseUser } from '../models/responses/ResponseUser';
|
||||||
import { ResponseUserPermissions } from '../models/responses/ResponseUserPermissions';
|
|
||||||
import { PermissionController } from './PermissionController';
|
import { PermissionController } from './PermissionController';
|
||||||
|
|
||||||
|
|
||||||
@ -26,19 +25,11 @@ export class UserController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authorized("USER:GET")
|
@Authorized("USER:GET")
|
||||||
@ResponseSchema(ResponseUser, { isArray: true })
|
@ResponseSchema(User, { 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 directly granted to them (if existing/associated).' })
|
||||||
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'] });
|
||||||
|
|
||||||
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));
|
||||||
});
|
});
|
||||||
@ -47,60 +38,38 @@ export class UserController {
|
|||||||
|
|
||||||
@Get('/:id')
|
@Get('/:id')
|
||||||
@Authorized("USER:GET")
|
@Authorized("USER:GET")
|
||||||
@ResponseSchema(ResponseUser)
|
@ResponseSchema(User)
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
||||||
@OnUndefined(UserNotFoundError)
|
@OnUndefined(UserNotFoundError)
|
||||||
@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that all permissions granted to the user will show up here.' })
|
@OpenAPI({ description: 'Lists all information about the user whose id got provided. <br> Please remember that only permissions granted directly to the user will show up here, not permissions inherited from groups.' })
|
||||||
async getOne(@Param('id') id: number) {
|
async getOne(@Param('id') id: number) {
|
||||||
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] })
|
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })
|
||||||
if (!user) { throw new UserNotFoundError(); }
|
if (!user) { throw new UserNotFoundError(); }
|
||||||
return new ResponseUser(user);
|
return new ResponseUser(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/:id/permissions')
|
|
||||||
@Authorized("USER:GET")
|
|
||||||
@ResponseSchema(ResponseUser)
|
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(UserNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all permissions granted to the user sorted into directly granted and inherited as permission response objects.' })
|
|
||||||
async getPermissions(@Param('id') id: number) {
|
|
||||||
let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions', 'permissions.principal', 'groups.permissions.principal'] })
|
|
||||||
if (!user) { throw new UserNotFoundError(); }
|
|
||||||
return new ResponseUserPermissions(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@Authorized("USER:CREATE")
|
@Authorized("USER:CREATE")
|
||||||
@ResponseSchema(ResponseUser)
|
@ResponseSchema(User)
|
||||||
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
|
@ResponseSchema(UserGroupNotFoundError)
|
||||||
@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;
|
||||||
try {
|
try {
|
||||||
user = await createUser.toEntity();
|
user = await createUser.toUser();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await this.userRepository.save(user)
|
user = await this.userRepository.save(user)
|
||||||
return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
|
return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups'] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('/:id')
|
@Put('/:id')
|
||||||
@Authorized("USER:UPDATE")
|
@Authorized("USER:UPDATE")
|
||||||
@ResponseSchema(ResponseUser)
|
@ResponseSchema(User)
|
||||||
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
@ResponseSchema(UserNotFoundError, { statusCode: 404 })
|
||||||
@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
|
@ResponseSchema(UserIdsNotMatchingError, { 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 });
|
||||||
@ -112,23 +81,21 @@ export class UserController {
|
|||||||
if (oldUser.id != updateUser.id) {
|
if (oldUser.id != updateUser.id) {
|
||||||
throw new UserIdsNotMatchingError();
|
throw new UserIdsNotMatchingError();
|
||||||
}
|
}
|
||||||
await this.userRepository.save(await updateUser.update(oldUser));
|
await this.userRepository.save(await updateUser.updateUser(oldUser));
|
||||||
|
|
||||||
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] }));
|
return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('/:id')
|
@Delete('/:id')
|
||||||
@Authorized("USER:DELETE")
|
@Authorized("USER:DELETE")
|
||||||
@ResponseSchema(ResponseUser)
|
@ResponseSchema(User)
|
||||||
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
@ResponseSchema(ResponseEmpty, { statusCode: 204 })
|
||||||
@ResponseSchema(UserDeletionNotConfirmedError, { statusCode: 406 })
|
|
||||||
@OnUndefined(204)
|
@OnUndefined(204)
|
||||||
@OpenAPI({ description: 'Delete the user whose id you provided. <br> You have to confirm your decision by providing the ?force=true query param. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
|
@OpenAPI({ description: 'Delete the user whose id you provided. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
||||||
if (!force) { throw new UserDeletionNotConfirmedError; }
|
|
||||||
let user = await this.userRepository.findOne({ id: id });
|
let user = await this.userRepository.findOne({ id: id });
|
||||||
if (!user) { return null; }
|
if (!user) { return null; }
|
||||||
const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups', 'groups.permissions'] });;
|
const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] });;
|
||||||
|
|
||||||
const permissionControler = new PermissionController();
|
const permissionControler = new PermissionController();
|
||||||
for (let permission of responseUser.permissions) {
|
for (let permission of responseUser.permissions) {
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
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 { EntityFromBody } from 'typeorm-routing-controllers-extensions';
|
||||||
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
|
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
|
||||||
import { CreateUserGroup } from '../models/actions/create/CreateUserGroup';
|
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
|
||||||
import { UpdateUserGroup } from '../models/actions/update/UpdateUserGroup';
|
|
||||||
import { UserGroup } from '../models/entities/UserGroup';
|
import { UserGroup } from '../models/entities/UserGroup';
|
||||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
|
||||||
import { ResponseUserGroup } from '../models/responses/ResponseUserGroup';
|
import { ResponseUserGroup } from '../models/responses/ResponseUserGroup';
|
||||||
import { ResponseUserGroupPermissions } from '../models/responses/ResponseUserGroupPermissions';
|
|
||||||
import { PermissionController } from './PermissionController';
|
import { PermissionController } from './PermissionController';
|
||||||
|
|
||||||
|
|
||||||
@ -25,44 +24,20 @@ export class UserGroupController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Authorized("USERGROUP:GET")
|
@Authorized("USERGROUP:GET")
|
||||||
@ResponseSchema(ResponseUserGroup, { isArray: true })
|
@ResponseSchema(UserGroup, { 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) {
|
getAll() {
|
||||||
let responseGroups: ResponseUserGroup[] = new Array<ResponseUserGroup>();
|
return this.userGroupsRepository.find({ relations: ["permissions"] });
|
||||||
let groups: Array<UserGroup>;
|
|
||||||
|
|
||||||
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 => {
|
|
||||||
responseGroups.push(group.toResponse());
|
|
||||||
});
|
|
||||||
return responseGroups;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/:id')
|
@Get('/:id')
|
||||||
@Authorized("USERGROUP:GET")
|
@Authorized("USERGROUP:GET")
|
||||||
@ResponseSchema(ResponseUserGroup)
|
@ResponseSchema(UserGroup)
|
||||||
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
|
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
|
||||||
@OnUndefined(UserGroupNotFoundError)
|
@OnUndefined(UserGroupNotFoundError)
|
||||||
@OpenAPI({ description: 'Lists all information about the group whose id got provided. <br> The information provided might change while the project continues to evolve.' })
|
@OpenAPI({ description: 'Lists all information about the group whose id got provided. <br> The information provided might change while the project continues to evolve.' })
|
||||||
async getOne(@Param('id') id: number) {
|
getOne(@Param('id') id: number) {
|
||||||
return await (await (this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] }))).toResponse();
|
return this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
|
||||||
}
|
|
||||||
|
|
||||||
@Get('/:id/permissions')
|
|
||||||
@Authorized("USERGROUP:GET")
|
|
||||||
@ResponseSchema(ResponseUserGroupPermissions)
|
|
||||||
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
|
|
||||||
@OnUndefined(UserGroupNotFoundError)
|
|
||||||
@OpenAPI({ description: 'Lists all permissions granted to the group as permission response objects.' })
|
|
||||||
async getPermissions(@Param('id') id: number) {
|
|
||||||
let group = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions', 'permissions.principal'] })
|
|
||||||
if (!group) { throw new UserGroupNotFoundError(); }
|
|
||||||
return new ResponseUserGroupPermissions(group);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ -73,13 +48,12 @@ export class UserGroupController {
|
|||||||
async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
|
async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
|
||||||
let userGroup;
|
let userGroup;
|
||||||
try {
|
try {
|
||||||
userGroup = await createUserGroup.toEntity();
|
userGroup = await createUserGroup.toUserGroup();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
userGroup = await this.userGroupsRepository.save(userGroup);
|
return this.userGroupsRepository.save(userGroup);
|
||||||
return (await (this.userGroupsRepository.findOne({ id: userGroup.id }, { relations: ["permissions"] }))).toResponse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('/:id')
|
@Put('/:id')
|
||||||
@ -88,19 +62,19 @@ export class UserGroupController {
|
|||||||
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
|
@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
|
||||||
@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 })
|
@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 })
|
||||||
@OpenAPI({ description: "Update the group whose id you provided. <br> To change the permissions granted to the group please use /api/permissions instead. <br> Please remember that ids can't be changed." })
|
@OpenAPI({ description: "Update the group whose id you provided. <br> To change the permissions granted to the group please use /api/permissions instead. <br> Please remember that ids can't be changed." })
|
||||||
async put(@Param('id') id: number, @Body({ validate: true }) updateGroup: UpdateUserGroup) {
|
async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) {
|
||||||
let oldGroup = await this.userGroupsRepository.findOne({ id: id });
|
let oldUserGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
|
||||||
|
|
||||||
if (!oldGroup) {
|
if (!oldUserGroup) {
|
||||||
throw new UserGroupNotFoundError();
|
throw new UserGroupNotFoundError()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldGroup.id != updateGroup.id) {
|
if (oldUserGroup.id != userGroup.id) {
|
||||||
throw new UserGroupIdsNotMatchingError();
|
throw new UserGroupIdsNotMatchingError();
|
||||||
}
|
}
|
||||||
await this.userGroupsRepository.save(await updateGroup.update(oldGroup));
|
|
||||||
|
|
||||||
return (await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] })).toResponse();
|
await this.userGroupsRepository.save(userGroup);
|
||||||
|
return userGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('/:id')
|
@Delete('/:id')
|
||||||
@ -110,13 +84,13 @@ export class UserGroupController {
|
|||||||
@OnUndefined(204)
|
@OnUndefined(204)
|
||||||
@OpenAPI({ description: 'Delete the group whose id you provided. <br> If there are any permissions directly granted to the group they will get deleted as well. <br> Users associated with this group won\'t get deleted - just deassociated. <br> If no group with this id exists it will just return 204(no content).' })
|
@OpenAPI({ description: 'Delete the group whose id you provided. <br> If there are any permissions directly granted to the group they will get deleted as well. <br> Users associated with this group won\'t get deleted - just deassociated. <br> If no group with this id exists it will just return 204(no content).' })
|
||||||
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
|
||||||
let group = await this.userGroupsRepository.findOne({ id: id });
|
let group = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
|
||||||
if (!group) { return null; }
|
if (!group) { return null; }
|
||||||
const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });
|
const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });
|
||||||
|
|
||||||
const permissionController = new PermissionController();
|
const permissionControler = new PermissionController();
|
||||||
for (let permission of responseGroup.permissions) {
|
for (let permission of responseGroup.permissions) {
|
||||||
await permissionController.remove(permission.id, true);
|
await permissionControler.remove(permission.id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.userGroupsRepository.delete(group);
|
await this.userGroupsRepository.delete(group);
|
||||||
|
@ -1,57 +1,24 @@
|
|||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
import { BadRequestError } from 'routing-controllers';
|
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when an address's postal code fails validation.
|
* Error to throw, when to provided address doesn't belong to the accepted types.
|
||||||
*/
|
*/
|
||||||
export class AddressPostalCodeInvalidError extends BadRequestError {
|
export class AddressWrongTypeError extends NotAcceptableError {
|
||||||
@IsString()
|
@IsString()
|
||||||
name = "AddressPostalCodeInvalidError"
|
name = "AddressWrongTypeError"
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "The postal code you provided is invalid. \n Please check if your postal code follows the postal code validation guidelines."
|
message = "The address must be an existing adress's id. \n You provided a object of another type."
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when an non-empty address's first line isn't set.
|
* Error to throw, when a non-existant address get's loaded.
|
||||||
*/
|
*/
|
||||||
export class AddressFirstLineEmptyError extends BadRequestError {
|
export class AddressNotFoundError extends NotFoundError {
|
||||||
@IsString()
|
@IsString()
|
||||||
name = "AddressFirstLineEmptyError"
|
name = "AddressNotFoundError"
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "You provided a empty first address line. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
|
message = "The address you provided couldn't be located in the system. \n Please check your request."
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when an non-empty address's postal code isn't set.
|
|
||||||
*/
|
|
||||||
export class AddressPostalCodeEmptyError extends BadRequestError {
|
|
||||||
@IsString()
|
|
||||||
name = "AddressPostalCodeEmptyError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "You provided a empty postal code. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when an non-empty address's city isn't set.
|
|
||||||
*/
|
|
||||||
export class AddressCityEmptyError extends BadRequestError {
|
|
||||||
@IsString()
|
|
||||||
name = "AddressCityEmptyError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "You provided a empty city. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when an non-empty address's country isn't set.
|
|
||||||
*/
|
|
||||||
export class AddressCountryEmptyError extends BadRequestError {
|
|
||||||
@IsString()
|
|
||||||
name = "AddressCountryEmptyError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "You provided a empty country. \n If you want an empty address please set all propertys to null. \n For non-empty addresses the following fields have to be set: address1, postalcode, city, country"
|
|
||||||
}
|
}
|
@ -118,7 +118,7 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when someone tries to reset a user's password more than once in 15 minutes.
|
* Error to throw when someone tryes to reset a user's password more than once in 15 minutes.
|
||||||
*/
|
*/
|
||||||
export class ResetAlreadyRequestedError extends NotAcceptableError {
|
export class ResetAlreadyRequestedError extends NotAcceptableError {
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a Donation couldn't be found.
|
|
||||||
*/
|
|
||||||
export class DonationNotFoundError extends NotFoundError {
|
|
||||||
@IsString()
|
|
||||||
name = "DonationNotFoundError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "Donation not found!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two Donations' ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a Donation's id.
|
|
||||||
*/
|
|
||||||
export class DonationIdsNotMatchingError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "DonationIdsNotMatchingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The ids don't match! \n And if you wanted to change a Donation's id: This isn't allowed!"
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a donor couldn't be found.
|
|
||||||
*/
|
|
||||||
export class DonorNotFoundError extends NotFoundError {
|
|
||||||
@IsString()
|
|
||||||
name = "DonorNotFoundError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "Donor not found!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two donors' ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a donor's id.
|
|
||||||
*/
|
|
||||||
export class DonorIdsNotMatchingError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "DonorIdsNotMatchingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The ids don't match! \n And if you wanted to change a donor's id: This isn't allowed!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a donor needs a receipt, but no address is associated with them.
|
|
||||||
*/
|
|
||||||
export class DonorReceiptAddressNeededError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "DonorReceiptAddressNeededError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "An address is needed to create a receipt for a donor. \n You didn't provide one."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a donor still has donations associated.
|
|
||||||
*/
|
|
||||||
export class DonorHasDonationsError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "DonorHasDonationsError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "This donor still has donations associated with it. \n If you want to delete this donor with all it's donations and teams add `?force` to your query."
|
|
||||||
}
|
|
@ -2,7 +2,18 @@ import { IsString } from 'class-validator';
|
|||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw, when a non-existent contact get's requested.
|
* Error to throw, when a provided groupContact doesn't belong to the accepted types.
|
||||||
|
*/
|
||||||
|
export class GroupContactWrongTypeError extends NotAcceptableError {
|
||||||
|
@IsString()
|
||||||
|
name = "GroupContactWrongTypeError"
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
message = "The groupContact must be an existing groupContact's id. \n You provided a object of another type."
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error to throw, when a non-existant groupContact get's loaded.
|
||||||
*/
|
*/
|
||||||
export class GroupContactNotFoundError extends NotFoundError {
|
export class GroupContactNotFoundError extends NotFoundError {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -10,16 +21,4 @@ export class GroupContactNotFoundError extends NotFoundError {
|
|||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "The groupContact you provided couldn't be located in the system. \n Please check your request."
|
message = "The groupContact you provided couldn't be located in the system. \n Please check your request."
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two contacts' ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a contact's id.
|
|
||||||
*/
|
|
||||||
export class GroupContactIdsNotMatchingError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "GroupContactIdsNotMatchingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The ids don't match! \n And if you wanted to change a contact's id: This isn't allowed!"
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { InternalServerError } from 'routing-controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a permission couldn't be found.
|
|
||||||
*/
|
|
||||||
export class MailSendingError extends InternalServerError {
|
|
||||||
@IsString()
|
|
||||||
name = "MailSendingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "We had a problem sending the mail!"
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super("We had a problem sending the mail!");
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,12 +13,12 @@ export class PrincipalNotFoundError extends NotFoundError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw, when a provided runner organization doesn't belong to the accepted types.
|
* Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
|
||||||
*/
|
*/
|
||||||
export class PrincipalWrongTypeError extends NotAcceptableError {
|
export class PrincipalWrongTypeError extends NotAcceptableError {
|
||||||
@IsString()
|
@IsString()
|
||||||
name = "PrincipalWrongTypeError"
|
name = "PrincipalWrongTypeError"
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "The principal must have an existing principal's id. \n You provided a object of another type."
|
message = "The princial must have an existing principal's id. \n You provided a object of another type."
|
||||||
}
|
}
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a card couldn't be found.
|
|
||||||
*/
|
|
||||||
export class RunnerCardNotFoundError extends NotFoundError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerCardNotFoundError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "Card not found!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two cards' ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a card's id.
|
|
||||||
*/
|
|
||||||
export class RunnerCardIdsNotMatchingError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerCardIdsNotMatchingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The ids don't match! \n And if you wanted to change a cards's id: This isn't allowed"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a card still has scans associated.
|
|
||||||
*/
|
|
||||||
export class RunnerCardHasScansError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerCardHasScansError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "This card still has scans associated with it. \n If you want to delete this card with all it's scans add `?force` to your query. \n Otherwise please consider just disabling it."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a card's id is too big to generate a ean-13 barcode for it.
|
|
||||||
* This error should never reach a end user.
|
|
||||||
*/
|
|
||||||
export class RunnerCardIdOutOfRangeError extends Error {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerCardIdOutOfRangeError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The card's id is too big to fit into a ean-13 barcode. \n This has a very low probability of happening but means that you might want to switch your barcode format for something that can accept numbers over 9999999999."
|
|
||||||
}
|
|
@ -32,38 +32,5 @@ export class RunnerGroupNeededError extends NotAcceptableError {
|
|||||||
name = "RunnerGroupNeededError"
|
name = "RunnerGroupNeededError"
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "Runner's need to be part of one group (team or organization)! \n You provided neither."
|
message = "Runner's need to be part of one group (team or organisiation)! \n You provided neither."
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a citizen runner has no mail-address.
|
|
||||||
*/
|
|
||||||
export class RunnerEmailNeededError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerEmailNeededError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "Citizenrunners have to provide an email address for verification and contacting."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a runner already requested a new selfservice link in the last 24hrs.
|
|
||||||
*/
|
|
||||||
export class RunnerSelfserviceTimeoutError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerSelfserviceTimeoutError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "You can only reqest a new token every 24hrs."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a runner still has distance donations associated.
|
|
||||||
*/
|
|
||||||
export class RunnerHasDistanceDonationsError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerHasDistanceDonationsError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "This runner still has distance donations associated with it. \n If you want to delete this runner with all it's donations and teams add `?force` to your query."
|
|
||||||
}
|
}
|
58
src/errors/RunnerOrganisationErrors.ts
Normal file
58
src/errors/RunnerOrganisationErrors.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { IsString } from 'class-validator';
|
||||||
|
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error to throw when a runner organisation couldn't be found.
|
||||||
|
*/
|
||||||
|
export class RunnerOrganisationNotFoundError extends NotFoundError {
|
||||||
|
@IsString()
|
||||||
|
name = "RunnerOrganisationNotFoundError"
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
message = "RunnerOrganisation not found!"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error to throw when two runner organisations' ids don't match.
|
||||||
|
* Usually occurs when a user tries to change a runner organisation's id.
|
||||||
|
*/
|
||||||
|
export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError {
|
||||||
|
@IsString()
|
||||||
|
name = "RunnerOrganisationIdsNotMatchingError"
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
message = "The ids don't match! \n And if you wanted to change a runner organisation's id: This isn't allowed!"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error to throw when a organisation still has runners associated.
|
||||||
|
*/
|
||||||
|
export class RunnerOrganisationHasRunnersError extends NotAcceptableError {
|
||||||
|
@IsString()
|
||||||
|
name = "RunnerOrganisationHasRunnersError"
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error to throw when a organisation still has teams associated.
|
||||||
|
*/
|
||||||
|
export class RunnerOrganisationHasTeamsError extends NotAcceptableError {
|
||||||
|
@IsString()
|
||||||
|
name = "RunnerOrganisationHasTeamsError"
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
|
||||||
|
*/
|
||||||
|
export class RunnerOrganisationWrongTypeError extends NotAcceptableError {
|
||||||
|
@IsString()
|
||||||
|
name = "RunnerOrganisationWrongTypeError"
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
message = "The runner organisation must be an existing organisation's id. \n You provided a object of another type."
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a runner organization couldn't be found.
|
|
||||||
*/
|
|
||||||
export class RunnerOrganizationNotFoundError extends NotFoundError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerOrganizationNotFoundError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "RunnerOrganization not found!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two runner organization's ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a runner organization's id.
|
|
||||||
*/
|
|
||||||
export class RunnerOrganizationIdsNotMatchingError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerOrganizationIdsNotMatchingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The ids don't match! \n And if you wanted to change a runner organization's id: This isn't allowed!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a organization still has runners associated.
|
|
||||||
*/
|
|
||||||
export class RunnerOrganizationHasRunnersError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerOrganizationHasRunnersError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "This organization still has runners associated with it. \n If you want to delete this organization with all it's runners and teams add `?force` to your query."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a organization still has teams associated.
|
|
||||||
*/
|
|
||||||
export class RunnerOrganizationHasTeamsError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerOrganizationHasTeamsError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "This organization still has teams associated with it. \n If you want to delete this organization with all it's runners and teams add `?force` to your query."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw, when a provided runnerOrganization doesn't belong to the accepted types.
|
|
||||||
*/
|
|
||||||
export class RunnerOrganizationWrongTypeError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "RunnerOrganizationWrongTypeError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The runner organization must be an existing organization's id. \n You provided a object of another type."
|
|
||||||
}
|
|
@ -43,5 +43,5 @@ export class RunnerTeamNeedsParentError extends NotAcceptableError {
|
|||||||
name = "RunnerTeamNeedsParentError"
|
name = "RunnerTeamNeedsParentError"
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "You provided no runner organization as this team's parent group."
|
message = "You provided no runner organisation as this team's parent group."
|
||||||
}
|
}
|
@ -1,25 +0,0 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a Scan couldn't be found.
|
|
||||||
*/
|
|
||||||
export class ScanNotFoundError extends NotFoundError {
|
|
||||||
@IsString()
|
|
||||||
name = "ScanNotFoundError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "Scan not found!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two Scans' ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a Scan's id.
|
|
||||||
*/
|
|
||||||
export class ScanIdsNotMatchingError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "ScanIdsNotMatchingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The ids don't match! \n And if you wanted to change a Scan's id: This isn't allowed!"
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
import { IsString } from 'class-validator';
|
|
||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw, when a non-existent scan station get's loaded.
|
|
||||||
*/
|
|
||||||
export class ScanStationNotFoundError extends NotFoundError {
|
|
||||||
@IsString()
|
|
||||||
name = "ScanStationNotFoundError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The scan station you provided couldn't be located in the system. \n Please check your request."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two scan stations' ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a scan station's id.
|
|
||||||
*/
|
|
||||||
export class ScanStationIdsNotMatchingError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "ScanStationIdsNotMatchingError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The ids don't match! \n And if you wanted to change a scan station's id: This isn't allowed!"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a station still has scans associated.
|
|
||||||
*/
|
|
||||||
export class ScanStationHasScansError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "ScanStationHasScansError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "This station still has scans associated with it. \n If you want to delete this station with all it's scans add `?force` to your query."
|
|
||||||
}
|
|
@ -2,7 +2,7 @@ import { IsString } from 'class-validator';
|
|||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw, when a non-existent stats client get's loaded.
|
* Error to throw, when a non-existant stats client get's loaded.
|
||||||
*/
|
*/
|
||||||
export class StatsClientNotFoundError extends NotFoundError {
|
export class StatsClientNotFoundError extends NotFoundError {
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -22,23 +22,4 @@ export class TrackIdsNotMatchingError extends NotAcceptableError {
|
|||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed"
|
message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed"
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when a track's lap time is set to a negative value.
|
|
||||||
*/
|
|
||||||
export class TrackLapTimeCantBeNegativeError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "TrackLapTimeCantBeNegativeError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The minimum lap time you provided is negative - That isn't possible. \n If you wanted to disable it: Just set it to 0/null."
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TrackHasScanStationsError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "TrackHasScanStationsError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "This track still has stations associated with it. \n If you want to delete this track with all it's stations and scans add `?force` to your query."
|
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when no username or email is set.
|
* Error to throw when no username or email is set.
|
||||||
* We somehow need to identify you on login.
|
* We somehow need to identify you :)
|
||||||
*/
|
*/
|
||||||
export class UsernameOrEmailNeededError extends NotFoundError {
|
export class UsernameOrEmailNeededError extends NotFoundError {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -14,30 +14,6 @@ export class UsernameOrEmailNeededError extends NotFoundError {
|
|||||||
message = "No username or email is set!"
|
message = "No username or email is set!"
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when no username contains illegal characters.
|
|
||||||
* Right now the only one is "@" but this could change in the future.
|
|
||||||
*/
|
|
||||||
export class UsernameContainsIllegalCharacterError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "UsernameContainsIllegalCharacterError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "The provided username contains illegal characters! \n Right now the following characters are considered illegal: '@'"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when no email is set.
|
|
||||||
* We somehow need to identify you :)
|
|
||||||
*/
|
|
||||||
export class UserEmailNeededError extends NotFoundError {
|
|
||||||
@IsString()
|
|
||||||
name = "UserEmailNeededError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "No email is set! \n You have to provide email addresses for users (used for password reset among others)."
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when a user couldn't be found.
|
* Error to throw when a user couldn't be found.
|
||||||
*/
|
*/
|
||||||
@ -59,45 +35,4 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
|
|||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
|
message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Error to throw when two users' ids don't match.
|
|
||||||
* Usually occurs when a user tries to change a user's id.
|
|
||||||
*/
|
|
||||||
export class UserDeletionNotConfirmedError extends NotAcceptableError {
|
|
||||||
@IsString()
|
|
||||||
name = "UserDeletionNotConfirmedError"
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
message = "You are trying to delete a user! \n If you're sure about doing this: provide the ?force=true query param."
|
|
||||||
}
|
|
||||||
|
|
||||||
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."
|
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ import { IsString } from 'class-validator';
|
|||||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when no group name is set.
|
* Error to throw when no groupname is set.
|
||||||
*/
|
*/
|
||||||
export class GroupNameNeededError extends NotFoundError {
|
export class GroupNameNeededError extends NotFoundError {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -13,7 +13,7 @@ export class GroupNameNeededError extends NotFoundError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when a user group couldn't be found.
|
* Error to throw when a usergroup couldn't be found.
|
||||||
*/
|
*/
|
||||||
export class UserGroupNotFoundError extends NotFoundError {
|
export class UserGroupNotFoundError extends NotFoundError {
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -24,13 +24,13 @@ export class UserGroupNotFoundError extends NotFoundError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error to throw when two user groups' ids don't match.
|
* Error to throw when two usergroups' ids don't match.
|
||||||
* Usually occurs when a user tries to change a user groups's id.
|
* Usually occurs when a user tries to change a usergroups's id.
|
||||||
*/
|
*/
|
||||||
export class UserGroupIdsNotMatchingError extends NotAcceptableError {
|
export class UserGroupIdsNotMatchingError extends NotAcceptableError {
|
||||||
@IsString()
|
@IsString()
|
||||||
name = "UserGroupIdsNotMatchingError"
|
name = "UserGroupIdsNotMatchingError"
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
message = "The ids don't match!! \n If you wanted to change a user group's id: This isn't allowed!"
|
message = "The ids don't match!! \n If you wanted to change a usergroup's id: This isn't allowed!"
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||||
import * as jsonwebtoken from "jsonwebtoken";
|
import * as jsonwebtoken from "jsonwebtoken";
|
||||||
import { config } from './config';
|
import { config } from './config';
|
||||||
import { Runner } from './models/entities/Runner';
|
|
||||||
import { User } from './models/entities/User';
|
import { User } from './models/entities/User';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,19 +34,6 @@ export class JwtCreator {
|
|||||||
}, config.jwt_secret)
|
}, config.jwt_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new selfservice token for a given runner.
|
|
||||||
* @param runner Runner entity that the access token shall be created for.
|
|
||||||
* @param expiry_timestamp Timestamp for the token expiry. Will be set about 9999 years if none provided.
|
|
||||||
*/
|
|
||||||
public static createSelfService(runner: Runner, expiry_timestamp?: number) {
|
|
||||||
if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 36000 * 60 * 24 * 365 * 9999; }
|
|
||||||
return jsonwebtoken.sign({
|
|
||||||
id: runner.id,
|
|
||||||
exp: expiry_timestamp
|
|
||||||
}, config.jwt_secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new password reset token for a given user.
|
* Creates a new password reset token for a given user.
|
||||||
* The token is valid for 15 minutes or 1 use - whatever comes first.
|
* The token is valid for 15 minutes or 1 use - whatever comes first.
|
||||||
@ -120,6 +106,23 @@ export class JwtUser {
|
|||||||
this.refreshTokenCount = user.refreshTokenCount;
|
this.refreshTokenCount = user.refreshTokenCount;
|
||||||
this.uuid = user.uuid;
|
this.uuid = user.uuid;
|
||||||
this.profilePic = user.profilePic;
|
this.profilePic = user.profilePic;
|
||||||
this.permissions = user.allPermissions;
|
this.permissions = this.getPermissions(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handels getting the permissions granted to this user (direct or indirect).
|
||||||
|
* @param user User which's permissions shall be gotten.
|
||||||
|
*/
|
||||||
|
public getPermissions(user: User): string[] {
|
||||||
|
let returnPermissions: string[] = new Array<string>();
|
||||||
|
for (let permission of user.permissions) {
|
||||||
|
returnPermissions.push(permission.toString());
|
||||||
|
}
|
||||||
|
for (let group of user.groups) {
|
||||||
|
for (let permission of group.permissions) {
|
||||||
|
returnPermissions.push(permission.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(new Set(returnPermissions));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,6 @@
|
|||||||
import { createConnection } from "typeorm";
|
import { createConnection } from "typeorm";
|
||||||
import { runSeeder } from 'typeorm-seeding';
|
import { runSeeder } from 'typeorm-seeding';
|
||||||
import { config } from '../config';
|
import { User } from '../models/entities/User';
|
||||||
import { ConfigFlag } from '../models/entities/ConfigFlags';
|
|
||||||
import SeedPublicOrg from '../seeds/SeedPublicOrg';
|
|
||||||
import SeedTestRunners from '../seeds/SeedTestRunners';
|
|
||||||
import SeedUsers from '../seeds/SeedUsers';
|
import SeedUsers from '../seeds/SeedUsers';
|
||||||
/**
|
/**
|
||||||
* Loader for the database that creates the database connection and initializes the database tabels.
|
* Loader for the database that creates the database connection and initializes the database tabels.
|
||||||
@ -12,20 +9,8 @@ import SeedUsers from '../seeds/SeedUsers';
|
|||||||
export default async () => {
|
export default async () => {
|
||||||
const connection = await createConnection();
|
const connection = await createConnection();
|
||||||
await connection.synchronize();
|
await connection.synchronize();
|
||||||
|
if (await connection.getRepository(User).count() === 0) {
|
||||||
//The data seeding part
|
|
||||||
if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:user", value: "true" }))) {
|
|
||||||
await runSeeder(SeedUsers);
|
await runSeeder(SeedUsers);
|
||||||
await connection.getRepository(ConfigFlag).save({ option: "seeded:user", value: "true" });
|
|
||||||
}
|
}
|
||||||
if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:citizenorg", value: "true" }))) {
|
|
||||||
await runSeeder(SeedPublicOrg);
|
|
||||||
await connection.getRepository(ConfigFlag).save({ option: "seeded:citizenorg", value: "true" });
|
|
||||||
}
|
|
||||||
if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:testdata", value: "true" })) && config.seedTestData == true) {
|
|
||||||
await runSeeder(SeedTestRunners);
|
|
||||||
await connection.getRepository(ConfigFlag).save({ option: "seeded:testdata", value: "true" });
|
|
||||||
}
|
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
};
|
};
|
@ -1,8 +1,8 @@
|
|||||||
import { validationMetadatasToSchemas } from "@odit/class-validator-jsonschema";
|
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
|
||||||
import express, { Application } from "express";
|
import express, { Application } from "express";
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { getMetadataArgsStorage } from "routing-controllers";
|
import { getMetadataArgsStorage } from "routing-controllers";
|
||||||
import { generateSpec } from '../apispec';
|
import { routingControllersToSpec } from "routing-controllers-openapi";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
|
* Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
|
||||||
@ -15,7 +15,41 @@ export default async (app: Application) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//Spec creation based on the previously created schemas
|
//Spec creation based on the previously created schemas
|
||||||
const spec = generateSpec(storage, schemas);
|
const spec = routingControllersToSpec(
|
||||||
|
storage,
|
||||||
|
{
|
||||||
|
routePrefix: "/api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
components: {
|
||||||
|
schemas,
|
||||||
|
"securitySchemes": {
|
||||||
|
"AuthToken": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer",
|
||||||
|
"bearerFormat": "JWT",
|
||||||
|
description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
|
||||||
|
},
|
||||||
|
"RefreshTokenCookie": {
|
||||||
|
"type": "apiKey",
|
||||||
|
"in": "cookie",
|
||||||
|
"name": "lfk_backend__refresh_token",
|
||||||
|
description: "A cookie containing a JWT based refreh token. Attention: Doesn't work in swagger-ui. Use /api/auth/login or /api/auth/refresh to get one."
|
||||||
|
},
|
||||||
|
"StatsApiToken": {
|
||||||
|
"type": "http",
|
||||||
|
"scheme": "bearer",
|
||||||
|
description: "Api token that can be obtained by creating a new stats client (post to /api/statsclients)."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
description: "The the backend API for the LfK! runner system.",
|
||||||
|
title: "LfK! Backend API",
|
||||||
|
version: "0.0.5",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
app.get(["/api/docs/openapi.json", "/api/docs/swagger.json"], (req, res) => {
|
app.get(["/api/docs/openapi.json", "/api/docs/swagger.json"], (req, res) => {
|
||||||
res.json(spec);
|
res.json(spec);
|
||||||
});
|
});
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
import axios from 'axios';
|
|
||||||
import { config } from './config';
|
|
||||||
import { MailSendingError } from './errors/MailErrors';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is responsible for all things mail sending.
|
|
||||||
* This uses axios to communicate with the mailer api (https://git.odit.services/lfk/mailer).
|
|
||||||
*/
|
|
||||||
export class Mailer {
|
|
||||||
public static base: string = config.mailer_url;
|
|
||||||
public static key: string = config.mailer_key;
|
|
||||||
public static testing: boolean = config.testing;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function for sending a password reset mail.
|
|
||||||
* @param to_address The address the mail will be sent to. Should always get pulled from a user object.
|
|
||||||
* @param token The requested password reset token - will be combined with the app_url to generate a password reset link.
|
|
||||||
*/
|
|
||||||
public static async sendResetMail(to_address: string, token: string, locale: string = "en") {
|
|
||||||
try {
|
|
||||||
await axios.post(`${Mailer.base}/reset?locale=${locale}&key=${Mailer.key}`, {
|
|
||||||
address: to_address,
|
|
||||||
resetKey: token
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (Mailer.testing) { return true; }
|
|
||||||
throw new MailSendingError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function for sending a runner selfservice welcome mail.
|
|
||||||
* @param to_address The address the mail will be sent to. Should always get pulled from a runner object.
|
|
||||||
* @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link.
|
|
||||||
*/
|
|
||||||
public static async sendSelfserviceWelcomeMail(to_address: string, token: string, locale: string = "en") {
|
|
||||||
try {
|
|
||||||
await axios.post(`${Mailer.base}/registration?locale=${locale}&key=${Mailer.key}`, {
|
|
||||||
address: to_address,
|
|
||||||
selfserviceToken: token
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (Mailer.testing) { return true; }
|
|
||||||
throw new MailSendingError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function for sending a runner selfservice link forgotten mail.
|
|
||||||
* @param to_address The address the mail will be sent to. Should always get pulled from a runner object.
|
|
||||||
* @param token The requested selfservice token - will be combined with the app_url to generate a selfservice profile link.
|
|
||||||
*/
|
|
||||||
public static async sendSelfserviceForgottenMail(to_address: string, token: string, locale: string = "en") {
|
|
||||||
try {
|
|
||||||
await axios.post(`${Mailer.base}/registration_forgot?locale=${locale}&key=${Mailer.key}`, {
|
|
||||||
address: to_address,
|
|
||||||
selfserviceToken: token
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (Mailer.testing) { return true; }
|
|
||||||
throw new MailSendingError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom express middleware that appends the raw body to the request object.
|
* Custom express middleware that appends the raw body to the request obeject.
|
||||||
* Mainly used for parsing csvs from bodies.
|
* Mainly used for parsing csvs from boddies.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
|
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
import * as argon2 from "argon2";
|
|
||||||
import { Request, Response } from 'express';
|
|
||||||
import { getConnectionManager } from 'typeorm';
|
|
||||||
import { ScanStation } from '../models/entities/ScanStation';
|
|
||||||
import authchecker from './authchecker';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This middleware handles the authentication of scan station api tokens.
|
|
||||||
* The tokens have to be provided via Bearer authorization header.
|
|
||||||
* You have to manually use this middleware via @UseBefore(ScanAuth) instead of using @Authorized().
|
|
||||||
* @param req Express request object.
|
|
||||||
* @param res Express response object.
|
|
||||||
* @param next Next function to call on success.
|
|
||||||
*/
|
|
||||||
const ScanAuth = async (req: Request, res: Response, next: () => void) => {
|
|
||||||
let provided_token: string = req.headers["authorization"];
|
|
||||||
if (provided_token == "" || provided_token === undefined || provided_token === null) {
|
|
||||||
res.status(401).send({ http_code: 401, short: "no_token", message: "No api token provided." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
provided_token = provided_token.replace("Bearer ", "");
|
|
||||||
} catch (error) {
|
|
||||||
res.status(401).send({ http_code: 401, short: "no_token", message: "No valid jwt or api token provided." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let prefix = "";
|
|
||||||
try {
|
|
||||||
prefix = provided_token.split(".")[0];
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (prefix == "" || prefix == undefined || prefix == null) {
|
|
||||||
res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const station = await getConnectionManager().get().getRepository(ScanStation).findOne({ prefix: prefix });
|
|
||||||
if (!station) {
|
|
||||||
let user_authorized = false;
|
|
||||||
try {
|
|
||||||
let action = { request: req, response: res, context: null, next: next }
|
|
||||||
user_authorized = await authchecker(action, ["SCAN:CREATE"]);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
if (user_authorized == false) {
|
|
||||||
res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (station.enabled == false) {
|
|
||||||
res.status(401).send({ http_code: 401, short: "station_disabled", message: "Station is disabled." });
|
|
||||||
}
|
|
||||||
if (!(await argon2.verify(station.key, provided_token))) {
|
|
||||||
res.status(401).send({ http_code: 401, short: "invalid_token", message: "Api token non-existent or invalid syntax." });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
req.headers["station_id"] = station.id.toString();
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default ScanAuth;
|
|
@ -5,9 +5,8 @@ import { StatsClient } from '../models/entities/StatsClient';
|
|||||||
import authchecker from './authchecker';
|
import authchecker from './authchecker';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This middleware handles the authentication of stats client api tokens.
|
* This middleware handels the authentification of stats client api tokens.
|
||||||
* The tokens have to be provided via Bearer authorization header.
|
* The tokens have to be provided via Bearer auth header.
|
||||||
* You have to manually use this middleware via @UseBefore(StatsAuth) instead of using @Authorized().
|
|
||||||
* @param req Express request object.
|
* @param req Express request object.
|
||||||
* @param res Express response object.
|
* @param res Express response object.
|
||||||
* @param next Next function to call on success.
|
* @param next Next function to call on success.
|
||||||
@ -42,7 +41,7 @@ const StatsAuth = async (req: Request, res: Response, next: () => void) => {
|
|||||||
let user_authorized = false;
|
let user_authorized = false;
|
||||||
try {
|
try {
|
||||||
let action = { request: req, response: res, context: null, next: next }
|
let action = { request: req, response: res, context: null, next: next }
|
||||||
user_authorized = await authchecker(action, ["RUNNER:GET", "TEAM:GET", "ORGANIZATION:GET"]);
|
user_authorized = await authchecker(action, ["RUNNER:GET", "TEAM:GET", "ORGANISATION:GET"]);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (user_authorized == false) {
|
if (user_authorized == false) {
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import cookie from "cookie";
|
|
||||||
import * as jwt from "jsonwebtoken";
|
|
||||||
import { Action } from 'routing-controllers';
|
|
||||||
import { getConnectionManager } from 'typeorm';
|
|
||||||
import { config } from '../config';
|
|
||||||
import { IllegalJWTError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from '../errors/AuthError';
|
|
||||||
import { JwtCreator, JwtUser } from '../jwtcreator';
|
|
||||||
import { User } from '../models/entities/User';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO:
|
|
||||||
*/
|
|
||||||
const UserChecker = async (action: Action) => {
|
|
||||||
let jwtPayload = undefined
|
|
||||||
try {
|
|
||||||
let provided_token = "" + action.request.headers["authorization"].replace("Bearer ", "");
|
|
||||||
jwtPayload = <any>jwt.verify(provided_token, config.jwt_secret);
|
|
||||||
jwtPayload = jwtPayload["userdetails"];
|
|
||||||
} catch (error) {
|
|
||||||
jwtPayload = await refresh(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] })
|
|
||||||
if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
|
|
||||||
if (user.enabled == false) { throw new UserDisabledError(); }
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles soft-refreshing of access-tokens.
|
|
||||||
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
|
|
||||||
*/
|
|
||||||
const refresh = async (action: Action) => {
|
|
||||||
let refresh_token = undefined;
|
|
||||||
try {
|
|
||||||
refresh_token = cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"];
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
throw new IllegalJWTError();
|
|
||||||
}
|
|
||||||
|
|
||||||
let jwtPayload = undefined;
|
|
||||||
try {
|
|
||||||
jwtPayload = <any>jwt.verify(refresh_token, config.jwt_secret);
|
|
||||||
} catch (error) {
|
|
||||||
throw new IllegalJWTError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions', 'groups', 'groups.permissions'] })
|
|
||||||
if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
|
|
||||||
if (user.enabled == false) { throw new UserDisabledError(); }
|
|
||||||
|
|
||||||
let newAccess = JwtCreator.createAccess(user);
|
|
||||||
action.response.header("authorization", "Bearer " + newAccess);
|
|
||||||
|
|
||||||
return await new JwtUser(user);
|
|
||||||
}
|
|
||||||
export default UserChecker;
|
|
@ -8,7 +8,7 @@ import { JwtCreator, JwtUser } from '../jwtcreator';
|
|||||||
import { User } from '../models/entities/User';
|
import { User } from '../models/entities/User';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles authentication via jwt's (Bearer authorization header) for all api endpoints using the @Authorized decorator.
|
* Handels authorisation verification via jwt's for all api endpoints using the @Authorized decorator.
|
||||||
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
|
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
|
||||||
* @param permissions The permissions that the endpoint using @Authorized requires.
|
* @param permissions The permissions that the endpoint using @Authorized requires.
|
||||||
*/
|
*/
|
||||||
@ -43,7 +43,7 @@ const authchecker = async (action: Action, permissions: string[] | string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles soft-refreshing of access-tokens.
|
* Handels soft-refreshing of access-tokens.
|
||||||
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
|
* @param action Routing-Controllers action object that provides request and response objects among other stuff.
|
||||||
*/
|
*/
|
||||||
const refresh = async (action: Action) => {
|
const refresh = async (action: Action) => {
|
||||||
|
70
src/models/actions/CreateAddress.ts
Normal file
70
src/models/actions/CreateAddress.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { IsNotEmpty, IsOptional, IsPostalCode, IsString } from 'class-validator';
|
||||||
|
import { config } from '../../config';
|
||||||
|
import { Address } from '../entities/Address';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This classed is used to create a new Address entity from a json body (post request).
|
||||||
|
*/
|
||||||
|
export class CreateAddress {
|
||||||
|
/**
|
||||||
|
* The newaddress's description.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new address's first line.
|
||||||
|
* Containing the street and house number.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
address1: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new address's second line.
|
||||||
|
* Containing optional information.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
address2?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new address's postal code.
|
||||||
|
* This will get checked against the postal code syntax for the configured country.
|
||||||
|
* TODO: Implement the config option.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsPostalCode(config.postalcode_validation_countrycode)
|
||||||
|
postalcode: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new address's city.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
city: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new address's country.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
country: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Address entity from this.
|
||||||
|
*/
|
||||||
|
public toAddress(): Address {
|
||||||
|
let newAddress: Address = new Address();
|
||||||
|
|
||||||
|
newAddress.address1 = this.address1;
|
||||||
|
newAddress.address2 = this.address2;
|
||||||
|
newAddress.postalcode = this.postalcode;
|
||||||
|
newAddress.city = this.city;
|
||||||
|
newAddress.country = this.country;
|
||||||
|
|
||||||
|
return newAddress;
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
import * as argon2 from "argon2";
|
import * as argon2 from "argon2";
|
||||||
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
|
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
|
||||||
import { UsernameOrEmailNeededError } from '../../../errors/UserErrors';
|
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
|
||||||
import { JwtCreator } from '../../../jwtcreator';
|
import { JwtCreator } from '../../jwtcreator';
|
||||||
import { User } from '../../entities/User';
|
import { User } from '../entities/User';
|
||||||
import { ResponseAuth } from '../../responses/ResponseAuth';
|
import { Auth } from '../responses/ResponseAuth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to create auth credentials based on user credentials provided in a json body (post request).
|
* This class is used to create auth credentials based on user credentials provided in a json body (post request).
|
||||||
@ -42,8 +42,8 @@ export class CreateAuth {
|
|||||||
/**
|
/**
|
||||||
* Creates a new auth object based on this.
|
* Creates a new auth object based on this.
|
||||||
*/
|
*/
|
||||||
public async toAuth(): Promise<ResponseAuth> {
|
public async toAuth(): Promise<Auth> {
|
||||||
let newAuth: ResponseAuth = new ResponseAuth();
|
let newAuth: Auth = new Auth();
|
||||||
|
|
||||||
if (this.email === undefined && this.username === undefined) {
|
if (this.email === undefined && this.username === undefined) {
|
||||||
throw new UsernameOrEmailNeededError();
|
throw new UsernameOrEmailNeededError();
|
85
src/models/actions/CreateGroupContact.ts
Normal file
85
src/models/actions/CreateGroupContact.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
||||||
|
import { getConnectionManager } from 'typeorm';
|
||||||
|
import { config } from '../../config';
|
||||||
|
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
|
||||||
|
import { Address } from '../entities/Address';
|
||||||
|
import { GroupContact } from '../entities/GroupContact';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This classed is used to create a new Group entity from a json body (post request).
|
||||||
|
*/
|
||||||
|
export class CreateGroupContact {
|
||||||
|
/**
|
||||||
|
* The new contact's first name.
|
||||||
|
*/
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
firstname: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new contact's middle name.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
middlename?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new contact's last name.
|
||||||
|
*/
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
lastname: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new contact's address.
|
||||||
|
* Must be the address's id.
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
address?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contact's phone number.
|
||||||
|
* This will be validated against the configured country phone numer syntax (default: international).
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsPhoneNumber(config.phone_validation_countrycode)
|
||||||
|
phone?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contact's email address.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail()
|
||||||
|
email?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the new contact's address by it's id.
|
||||||
|
*/
|
||||||
|
public async getAddress(): Promise<Address> {
|
||||||
|
if (this.address === undefined || this.address === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isNaN(this.address)) {
|
||||||
|
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
|
||||||
|
if (!address) { throw new AddressNotFoundError; }
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AddressWrongTypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Address entity from this.
|
||||||
|
*/
|
||||||
|
public async toGroupContact(): Promise<GroupContact> {
|
||||||
|
let contact: GroupContact = new GroupContact();
|
||||||
|
contact.firstname = this.firstname;
|
||||||
|
contact.middlename = this.middlename;
|
||||||
|
contact.lastname = this.lastname;
|
||||||
|
contact.email = this.email;
|
||||||
|
contact.phone = this.phone;
|
||||||
|
contact.address = await this.getAddress();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
72
src/models/actions/CreateParticipant.ts
Normal file
72
src/models/actions/CreateParticipant.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
||||||
|
import { getConnectionManager } from 'typeorm';
|
||||||
|
import { config } from '../../config';
|
||||||
|
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
|
||||||
|
import { Address } from '../entities/Address';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This classed is used to create a new Participant entity from a json body (post request).
|
||||||
|
*/
|
||||||
|
export abstract class CreateParticipant {
|
||||||
|
/**
|
||||||
|
* The new participant's first name.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
firstname: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new participant's middle name.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
middlename?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new participant's last name.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
lastname: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new participant's phone number.
|
||||||
|
* This will be validated against the configured country phone numer syntax (default: international).
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
@IsPhoneNumber(config.phone_validation_countrycode)
|
||||||
|
phone?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new participant's e-mail address.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
@IsEmail()
|
||||||
|
email?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new participant's address.
|
||||||
|
* Must be of type number (address id).
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
address?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the new participant's address by it's address.
|
||||||
|
*/
|
||||||
|
public async getAddress(): Promise<Address> {
|
||||||
|
if (this.address === undefined || this.address === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isNaN(this.address)) {
|
||||||
|
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
|
||||||
|
if (!address) { throw new AddressNotFoundError; }
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AddressWrongTypeError;
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,11 @@ import {
|
|||||||
IsNotEmpty
|
IsNotEmpty
|
||||||
} from "class-validator";
|
} from "class-validator";
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
|
import { PrincipalNotFoundError } from '../../errors/PrincipalErrors';
|
||||||
import { Permission } from '../../entities/Permission';
|
import { Permission } from '../entities/Permission';
|
||||||
import { Principal } from '../../entities/Principal';
|
import { Principal } from '../entities/Principal';
|
||||||
import { PermissionAction } from '../../enums/PermissionAction';
|
import { PermissionAction } from '../enums/PermissionAction';
|
||||||
import { PermissionTarget } from '../../enums/PermissionTargets';
|
import { PermissionTarget } from '../enums/PermissionTargets';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This classed is used to create a new Permission entity from a json body (post request).
|
* This classed is used to create a new Permission entity from a json body (post request).
|
||||||
@ -39,7 +39,7 @@ export class CreatePermission {
|
|||||||
/**
|
/**
|
||||||
* Creates a new Permission entity from this.
|
* Creates a new Permission entity from this.
|
||||||
*/
|
*/
|
||||||
public async toEntity(): Promise<Permission> {
|
public async toPermission(): Promise<Permission> {
|
||||||
let newPermission: Permission = new Permission();
|
let newPermission: Permission = new Permission();
|
||||||
|
|
||||||
newPermission.principal = await this.getPrincipal();
|
newPermission.principal = await this.getPrincipal();
|
@ -1,33 +1,39 @@
|
|||||||
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
import { IsEmail, IsOptional, IsString } from 'class-validator';
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
|
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
|
||||||
import { UserEmailNeededError } from '../../../errors/UserErrors';
|
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
|
||||||
import { JwtCreator } from '../../../jwtcreator';
|
import { JwtCreator } from '../../jwtcreator';
|
||||||
import { User } from '../../entities/User';
|
import { User } from '../entities/User';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to create password reset tokens for users.
|
* This calss is used to create password reset tokens for users.
|
||||||
* These password reset token can be used to set a new password for the user for the next 15mins.
|
* These password reset token can be used to set a new password for the user for the next 15mins.
|
||||||
*/
|
*/
|
||||||
export class CreateResetToken {
|
export class CreateResetToken {
|
||||||
|
/**
|
||||||
|
* The username of the user that wants to reset their password.
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
username?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The email address of the user that wants to reset their password.
|
* The email address of the user that wants to reset their password.
|
||||||
*/
|
*/
|
||||||
@IsNotEmpty()
|
@IsOptional()
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsString()
|
@IsString()
|
||||||
email: string;
|
email?: string;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a password reset token based on this.
|
* Create a password reset token based on this.
|
||||||
*/
|
*/
|
||||||
public async toResetToken(): Promise<string> {
|
public async toResetToken(): Promise<any> {
|
||||||
if (!this.email) {
|
if (this.email === undefined && this.username === undefined) {
|
||||||
throw new UserEmailNeededError();
|
throw new UsernameOrEmailNeededError();
|
||||||
}
|
}
|
||||||
let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ email: this.email }] });
|
let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { email: this.email }] });
|
||||||
if (!found_user) { throw new UserNotFoundError(); }
|
if (!found_user) { throw new UserNotFoundError(); }
|
||||||
if (found_user.enabled == false) { throw new UserDisabledError(); }
|
if (found_user.enabled == false) { throw new UserDisabledError(); }
|
||||||
if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); }
|
if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); }
|
||||||
@ -37,7 +43,7 @@ export class CreateResetToken {
|
|||||||
await getConnectionManager().get().getRepository(User).save(found_user);
|
await getConnectionManager().get().getRepository(User).save(found_user);
|
||||||
|
|
||||||
//Create the reset token
|
//Create the reset token
|
||||||
let reset_token: string = JwtCreator.createReset(found_user);
|
let reset_token = JwtCreator.createReset(found_user);
|
||||||
|
|
||||||
return reset_token;
|
return reset_token;
|
||||||
}
|
}
|
@ -1,11 +1,10 @@
|
|||||||
import { IsInt } from 'class-validator';
|
import { IsInt } from 'class-validator';
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
|
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
|
||||||
import { RunnerOrganizationWrongTypeError } from '../../../errors/RunnerOrganizationErrors';
|
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
|
||||||
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
|
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
|
||||||
import { Address } from '../../entities/Address';
|
import { Runner } from '../entities/Runner';
|
||||||
import { Runner } from '../../entities/Runner';
|
import { RunnerGroup } from '../entities/RunnerGroup';
|
||||||
import { RunnerGroup } from '../../entities/RunnerGroup';
|
|
||||||
import { CreateParticipant } from './CreateParticipant';
|
import { CreateParticipant } from './CreateParticipant';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,7 +21,7 @@ export class CreateRunner extends CreateParticipant {
|
|||||||
/**
|
/**
|
||||||
* Creates a new Runner entity from this.
|
* Creates a new Runner entity from this.
|
||||||
*/
|
*/
|
||||||
public async toEntity(): Promise<Runner> {
|
public async toRunner(): Promise<Runner> {
|
||||||
let newRunner: Runner = new Runner();
|
let newRunner: Runner = new Runner();
|
||||||
|
|
||||||
newRunner.firstname = this.firstname;
|
newRunner.firstname = this.firstname;
|
||||||
@ -31,8 +30,7 @@ export class CreateRunner extends CreateParticipant {
|
|||||||
newRunner.phone = this.phone;
|
newRunner.phone = this.phone;
|
||||||
newRunner.email = this.email;
|
newRunner.email = this.email;
|
||||||
newRunner.group = await this.getGroup();
|
newRunner.group = await this.getGroup();
|
||||||
newRunner.address = this.address;
|
newRunner.address = await this.getAddress();
|
||||||
Address.validate(newRunner.address);
|
|
||||||
|
|
||||||
return newRunner;
|
return newRunner;
|
||||||
}
|
}
|
||||||
@ -50,6 +48,6 @@ export class CreateRunner extends CreateParticipant {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RunnerOrganizationWrongTypeError;
|
throw new RunnerOrganisationWrongTypeError;
|
||||||
}
|
}
|
||||||
}
|
}
|
40
src/models/actions/CreateRunnerGroup.ts
Normal file
40
src/models/actions/CreateRunnerGroup.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
import { getConnectionManager } from 'typeorm';
|
||||||
|
import { GroupContactNotFoundError, GroupContactWrongTypeError } from '../../errors/GroupContactErrors';
|
||||||
|
import { GroupContact } from '../entities/GroupContact';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This classed is used to create a new RunnerGroup entity from a json body (post request).
|
||||||
|
*/
|
||||||
|
export abstract class CreateRunnerGroup {
|
||||||
|
/**
|
||||||
|
* The new group's name.
|
||||||
|
*/
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new group's contact.
|
||||||
|
* Optional
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
contact?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the new group's contact by it's id.
|
||||||
|
*/
|
||||||
|
public async getContact(): Promise<GroupContact> {
|
||||||
|
if (this.contact === undefined || this.contact === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isNaN(this.contact)) {
|
||||||
|
let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
|
||||||
|
if (!contact) { throw new GroupContactNotFoundError; }
|
||||||
|
return contact;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new GroupContactWrongTypeError;
|
||||||
|
}
|
||||||
|
}
|
48
src/models/actions/CreateRunnerOrganisation.ts
Normal file
48
src/models/actions/CreateRunnerOrganisation.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { IsInt, IsOptional } from 'class-validator';
|
||||||
|
import { getConnectionManager } from 'typeorm';
|
||||||
|
import { AddressNotFoundError, AddressWrongTypeError } from '../../errors/AddressErrors';
|
||||||
|
import { Address } from '../entities/Address';
|
||||||
|
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
|
||||||
|
import { CreateRunnerGroup } from './CreateRunnerGroup';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This classed is used to create a new RunnerOrganisation entity from a json body (post request).
|
||||||
|
*/
|
||||||
|
export class CreateRunnerOrganisation extends CreateRunnerGroup {
|
||||||
|
/**
|
||||||
|
* The new organisation's address.
|
||||||
|
* Must be of type number (address id).
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
address?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the org's address by it's id.
|
||||||
|
*/
|
||||||
|
public async getAddress(): Promise<Address> {
|
||||||
|
if (this.address === undefined || this.address === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!isNaN(this.address)) {
|
||||||
|
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
|
||||||
|
if (!address) { throw new AddressNotFoundError; }
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AddressWrongTypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new RunnerOrganisation entity from this.
|
||||||
|
*/
|
||||||
|
public async toRunnerOrganisation(): Promise<RunnerOrganisation> {
|
||||||
|
let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
|
||||||
|
|
||||||
|
newRunnerOrganisation.name = this.name;
|
||||||
|
newRunnerOrganisation.contact = await this.getContact();
|
||||||
|
newRunnerOrganisation.address = await this.getAddress();
|
||||||
|
|
||||||
|
return newRunnerOrganisation;
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
import { IsInt, IsNotEmpty } from 'class-validator';
|
import { IsInt, IsNotEmpty } from 'class-validator';
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { RunnerOrganizationNotFoundError } from '../../../errors/RunnerOrganizationErrors';
|
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
|
||||||
import { RunnerTeamNeedsParentError } from '../../../errors/RunnerTeamErrors';
|
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
|
||||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
|
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
|
||||||
import { RunnerTeam } from '../../entities/RunnerTeam';
|
import { RunnerTeam } from '../entities/RunnerTeam';
|
||||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
|
import { CreateRunnerGroup } from './CreateRunnerGroup';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,7 +12,7 @@ import { CreateRunnerGroup } from './CreateRunnerGroup';
|
|||||||
export class CreateRunnerTeam extends CreateRunnerGroup {
|
export class CreateRunnerTeam extends CreateRunnerGroup {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The new team's parent org's id.
|
* The new team's parent group (organisation).
|
||||||
*/
|
*/
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
@ -21,23 +21,28 @@ export class CreateRunnerTeam extends CreateRunnerGroup {
|
|||||||
/**
|
/**
|
||||||
* Gets the new team's parent org based on it's id.
|
* Gets the new team's parent org based on it's id.
|
||||||
*/
|
*/
|
||||||
public async getParent(): Promise<RunnerOrganization> {
|
public async getParent(): Promise<RunnerOrganisation> {
|
||||||
if (this.parentGroup === undefined || this.parentGroup === null) {
|
if (this.parentGroup === undefined || this.parentGroup === null) {
|
||||||
throw new RunnerTeamNeedsParentError();
|
throw new RunnerTeamNeedsParentError();
|
||||||
}
|
}
|
||||||
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: this.parentGroup });
|
if (!isNaN(this.parentGroup)) {
|
||||||
if (!parentGroup) { throw new RunnerOrganizationNotFoundError();; }
|
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup });
|
||||||
return parentGroup;
|
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
|
||||||
|
return parentGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RunnerOrganisationWrongTypeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new RunnerTeam entity from this.
|
* Creates a new RunnerTeam entity from this.
|
||||||
*/
|
*/
|
||||||
public async toEntity(): Promise<RunnerTeam> {
|
public async toRunnerTeam(): Promise<RunnerTeam> {
|
||||||
let newRunnerTeam: RunnerTeam = new RunnerTeam();
|
let newRunnerTeam: RunnerTeam = new RunnerTeam();
|
||||||
|
|
||||||
newRunnerTeam.name = this.name;
|
newRunnerTeam.name = this.name;
|
||||||
newRunnerTeam.parentGroup = await this.getParent();
|
newRunnerTeam.parentGroup = await this.getParent();
|
||||||
|
|
||||||
newRunnerTeam.contact = await this.getContact()
|
newRunnerTeam.contact = await this.getContact()
|
||||||
|
|
||||||
return newRunnerTeam;
|
return newRunnerTeam;
|
@ -2,7 +2,7 @@ import * as argon2 from "argon2";
|
|||||||
import { IsOptional, IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import { StatsClient } from '../../entities/StatsClient';
|
import { StatsClient } from '../entities/StatsClient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This classed is used to create a new StatsClient entity from a json body (post request).
|
* This classed is used to create a new StatsClient entity from a json body (post request).
|
||||||
@ -18,7 +18,7 @@ export class CreateStatsClient {
|
|||||||
/**
|
/**
|
||||||
* Converts this to a StatsClient entity.
|
* Converts this to a StatsClient entity.
|
||||||
*/
|
*/
|
||||||
public async toEntity(): Promise<StatsClient> {
|
public async toStatsClient(): Promise<StatsClient> {
|
||||||
let newClient: StatsClient = new StatsClient();
|
let newClient: StatsClient = new StatsClient();
|
||||||
|
|
||||||
newClient.description = this.description;
|
newClient.description = this.description;
|
33
src/models/actions/CreateTrack.ts
Normal file
33
src/models/actions/CreateTrack.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
|
||||||
|
import { Track } from '../entities/Track';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This classed is used to create a new Track entity from a json body (post request).
|
||||||
|
*/
|
||||||
|
export class CreateTrack {
|
||||||
|
/**
|
||||||
|
* The new track's name.
|
||||||
|
*/
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The new track's distance in meters (must be greater than 0).
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
@IsPositive()
|
||||||
|
distance: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Track entity from this.
|
||||||
|
*/
|
||||||
|
public toTrack(): Track {
|
||||||
|
let newTrack: Track = new Track();
|
||||||
|
|
||||||
|
newTrack.name = this.name;
|
||||||
|
newTrack.distance = this.distance;
|
||||||
|
|
||||||
|
return newTrack;
|
||||||
|
}
|
||||||
|
}
|
@ -1,139 +1,124 @@
|
|||||||
import * as argon2 from "argon2";
|
import * as argon2 from "argon2";
|
||||||
import { passwordStrength } from "check-password-strength";
|
import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString } 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 { UsernameOrEmailNeededError } from '../../errors/UserErrors';
|
||||||
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, 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 classed is used to create a new User entity from a json body (post request).
|
||||||
* This classed is used to create a new User entity from a json body (post request).
|
*/
|
||||||
*/
|
export class CreateUser {
|
||||||
export class CreateUser {
|
/**
|
||||||
/**
|
* The new user's first name.
|
||||||
* The new user's first name.
|
*/
|
||||||
*/
|
@IsString()
|
||||||
@IsString()
|
firstname: string;
|
||||||
firstname: string;
|
|
||||||
|
/**
|
||||||
/**
|
* The new user's middle name.
|
||||||
* The new user's middle name.
|
*/
|
||||||
*/
|
@IsString()
|
||||||
@IsString()
|
@IsOptional()
|
||||||
@IsOptional()
|
middlename?: string;
|
||||||
middlename?: string;
|
|
||||||
|
/**
|
||||||
/**
|
* The new user's last name.
|
||||||
* The new user's last name.
|
*/
|
||||||
*/
|
@IsString()
|
||||||
@IsString()
|
lastname: string;
|
||||||
lastname: string;
|
|
||||||
|
/**
|
||||||
/**
|
* The new user's username.
|
||||||
* The new user's username.
|
* You have to provide at least one of: {email, username}.
|
||||||
* You have to provide a email addres, so this is optional.
|
*/
|
||||||
*/
|
@IsOptional()
|
||||||
@IsOptional()
|
@IsString()
|
||||||
@IsString()
|
username?: string;
|
||||||
username?: string;
|
|
||||||
|
/**
|
||||||
/**
|
* The new user's email address.
|
||||||
* The new user's email address.
|
* You have to provide at least one of: {email, username}.
|
||||||
*/
|
*/
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsOptional()
|
||||||
email: string;
|
email?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The new user's phone number.
|
* The new user's phone number.
|
||||||
* This will be validated against the configured country phone numer syntax (default: international).
|
* This will be validated against the configured country phone numer syntax (default: international).
|
||||||
*/
|
*/
|
||||||
@IsPhoneNumber(config.phone_validation_countrycode)
|
@IsPhoneNumber(config.phone_validation_countrycode)
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
phone?: string;
|
phone?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The new user's password.
|
* The new user's password.
|
||||||
* This will of course not be saved in plaintext :)
|
* This will of course not be saved in plaintext :)
|
||||||
*/
|
*/
|
||||||
@IsString()
|
@IsString()
|
||||||
password: string;
|
password: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will the new user be enabled from the start?
|
* Will the new user be enabled from the start?
|
||||||
* Default: true
|
* Default: true
|
||||||
*/
|
*/
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
enabled?: boolean = true;
|
enabled?: boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The new user's groups' ids.
|
* The new user's groups' id(s).
|
||||||
* You can provide either one groupId or an array of groupIDs.
|
* You can provide either one groupId or an array of groupIDs.
|
||||||
*/
|
*/
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
groups?: number[] | number
|
groups?: number[] | number
|
||||||
|
|
||||||
/**
|
//TODO: ProfilePics
|
||||||
* The user's profile pic (or rather a url pointing to it).
|
|
||||||
*/
|
/**
|
||||||
@IsString()
|
* Converts this to a User entity.
|
||||||
@IsUrl()
|
*/
|
||||||
@IsOptional()
|
public async toUser(): Promise<User> {
|
||||||
profilePic?: string;
|
let newUser: User = new User();
|
||||||
|
|
||||||
/**
|
if (this.email === undefined && this.username === undefined) {
|
||||||
* Converts this to a User entity.
|
throw new UsernameOrEmailNeededError();
|
||||||
*/
|
}
|
||||||
public async toEntity(): Promise<User> {
|
|
||||||
let newUser: User = new User();
|
newUser.email = this.email
|
||||||
|
newUser.username = this.username
|
||||||
if (!this.email) {
|
newUser.firstname = this.firstname
|
||||||
throw new UserEmailNeededError();
|
newUser.middlename = this.middlename
|
||||||
}
|
newUser.lastname = this.lastname
|
||||||
if (this.username?.includes("@")) { throw new UsernameContainsIllegalCharacterError(); }
|
newUser.uuid = uuid.v4()
|
||||||
|
newUser.phone = this.phone
|
||||||
let password_strength = passwordStrength(this.password);
|
newUser.password = await argon2.hash(this.password + newUser.uuid);
|
||||||
if (!password_strength.contains.includes("uppercase")) { throw new PasswordMustContainUppercaseLetterError(); }
|
newUser.groups = await this.getGroups();
|
||||||
if (!password_strength.contains.includes("lowercase")) { throw new PasswordMustContainLowercaseLetterError(); }
|
newUser.enabled = this.enabled;
|
||||||
if (!password_strength.contains.includes("number")) { throw new PasswordMustContainNumberError(); }
|
//TODO: ProfilePics
|
||||||
if (!(password_strength.length > 9)) { throw new PasswordTooShortError(); }
|
|
||||||
|
return newUser;
|
||||||
newUser.email = this.email
|
}
|
||||||
newUser.username = this.username
|
|
||||||
newUser.firstname = this.firstname
|
/**
|
||||||
newUser.middlename = this.middlename
|
* Get's all groups for this user by their id's;
|
||||||
newUser.lastname = this.lastname
|
*/
|
||||||
newUser.uuid = uuid.v4()
|
public async getGroups() {
|
||||||
newUser.phone = this.phone
|
if (!this.groups) { return null; }
|
||||||
newUser.password = await argon2.hash(this.password + newUser.uuid);
|
let groups = new Array<UserGroup>();
|
||||||
newUser.groups = await this.getGroups();
|
if (!Array.isArray(this.groups)) {
|
||||||
newUser.enabled = this.enabled;
|
this.groups = [this.groups]
|
||||||
|
}
|
||||||
if (!this.profilePic) { newUser.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; }
|
for (let group of this.groups) {
|
||||||
else { newUser.profilePic = this.profilePic; }
|
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
|
||||||
|
if (!found) { throw new UserGroupNotFoundError(); }
|
||||||
return newUser;
|
groups.push(found);
|
||||||
}
|
}
|
||||||
|
return groups;
|
||||||
/**
|
}
|
||||||
* Get's all groups for this user by their id's;
|
|
||||||
*/
|
|
||||||
public async getGroups() {
|
|
||||||
if (!this.groups) { return null; }
|
|
||||||
let groups = new Array<UserGroup>();
|
|
||||||
if (!Array.isArray(this.groups)) {
|
|
||||||
this.groups = [this.groups]
|
|
||||||
}
|
|
||||||
for (let group of this.groups) {
|
|
||||||
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
|
|
||||||
if (!found) { throw new UserGroupNotFoundError(); }
|
|
||||||
groups.push(found);
|
|
||||||
}
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import { IsOptional, IsString } from 'class-validator';
|
import { IsOptional, IsString } from 'class-validator';
|
||||||
import { UserGroup } from '../../entities/UserGroup';
|
import { UserGroup } from '../entities/UserGroup';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This classed is used to create a new UserGroup entity from a json body (post request).
|
* This classed is used to create a new UserGroup entity from a json body (post request).
|
||||||
@ -22,7 +22,7 @@ export class CreateUserGroup {
|
|||||||
/**
|
/**
|
||||||
* Creates a new UserGroup entity from this.
|
* Creates a new UserGroup entity from this.
|
||||||
*/
|
*/
|
||||||
public async toEntity(): Promise<UserGroup> {
|
public async toUserGroup(): Promise<UserGroup> {
|
||||||
let newUserGroup: UserGroup = new UserGroup();
|
let newUserGroup: UserGroup = new UserGroup();
|
||||||
|
|
||||||
newUserGroup.name = this.name;
|
newUserGroup.name = this.name;
|
@ -1,11 +1,11 @@
|
|||||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { RunnerGroupNeededError } from '../../errors/RunnerErrors';
|
import { RunnerGroupNeededError } from '../../errors/RunnerErrors';
|
||||||
import { RunnerOrganizationNotFoundError } from '../../errors/RunnerOrganizationErrors';
|
import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisationErrors';
|
||||||
import { RunnerGroup } from '../entities/RunnerGroup';
|
import { RunnerGroup } from '../entities/RunnerGroup';
|
||||||
import { RunnerOrganization } from '../entities/RunnerOrganization';
|
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
|
||||||
import { RunnerTeam } from '../entities/RunnerTeam';
|
import { RunnerTeam } from '../entities/RunnerTeam';
|
||||||
import { CreateRunner } from './create/CreateRunner';
|
import { CreateRunner } from './CreateRunner';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Special class used to import runners from csv files - or json arrays created from csv to be exact.
|
* Special class used to import runners from csv files - or json arrays created from csv to be exact.
|
||||||
@ -78,9 +78,9 @@ export class ImportRunner {
|
|||||||
let team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: groupID });
|
let team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: groupID });
|
||||||
if (team) { return team; }
|
if (team) { return team; }
|
||||||
|
|
||||||
let org = await getConnectionManager().get().getRepository(RunnerOrganization).findOne({ id: groupID });
|
let org = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: groupID });
|
||||||
if (!org) {
|
if (!org) {
|
||||||
throw new RunnerOrganizationNotFoundError();
|
throw new RunnerOrganisationNotFoundError();
|
||||||
}
|
}
|
||||||
if (this.team === undefined) { return org; }
|
if (this.team === undefined) { return org; }
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import { config } from '../../config';
|
|||||||
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
|
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
|
||||||
import { JwtCreator } from "../../jwtcreator";
|
import { JwtCreator } from "../../jwtcreator";
|
||||||
import { User } from '../entities/User';
|
import { User } from '../entities/User';
|
||||||
import { ResponseAuth } from '../responses/ResponseAuth';
|
import { Auth } from '../responses/ResponseAuth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to create refreshed auth credentials.
|
* This class is used to create refreshed auth credentials.
|
||||||
@ -24,8 +24,8 @@ export class RefreshAuth {
|
|||||||
/**
|
/**
|
||||||
* Creates a new auth object based on this.
|
* Creates a new auth object based on this.
|
||||||
*/
|
*/
|
||||||
public async toAuth(): Promise<ResponseAuth> {
|
public async toAuth(): Promise<Auth> {
|
||||||
let newAuth: ResponseAuth = new ResponseAuth();
|
let newAuth: Auth = new Auth();
|
||||||
if (!this.token || this.token === undefined) {
|
if (!this.token || this.token === undefined) {
|
||||||
throw new JwtNotProvidedError()
|
throw new JwtNotProvidedError()
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { IsInt, IsNotEmpty, IsPositive } from 'class-validator';
|
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { PermissionNeedsPrincipalError } from '../../../errors/PermissionErrors';
|
import { PermissionNeedsPrincipalError } from '../../errors/PermissionErrors';
|
||||||
import { PrincipalNotFoundError } from '../../../errors/PrincipalErrors';
|
import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../errors/PrincipalErrors';
|
||||||
import { Permission } from '../../entities/Permission';
|
import { Permission } from '../entities/Permission';
|
||||||
import { Principal } from '../../entities/Principal';
|
import { Principal } from '../entities/Principal';
|
||||||
import { PermissionAction } from '../../enums/PermissionAction';
|
import { PermissionAction } from '../enums/PermissionAction';
|
||||||
import { PermissionTarget } from '../../enums/PermissionTargets';
|
import { PermissionTarget } from '../enums/PermissionTargets';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to update a Permission entity (via put request).
|
* This class is used to update a Permission entity (via put request).
|
||||||
@ -20,11 +20,12 @@ export class UpdatePermission {
|
|||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The updated permissions's principal's id.
|
* The updated permissions's principal.
|
||||||
|
* Just has to contain the principal's id -everything else won't be checked or changed.
|
||||||
*/
|
*/
|
||||||
@IsInt()
|
@IsObject()
|
||||||
@IsPositive()
|
@IsNotEmpty()
|
||||||
principal: number;
|
principal: Principal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The permissions's target.
|
* The permissions's target.
|
||||||
@ -41,7 +42,7 @@ export class UpdatePermission {
|
|||||||
/**
|
/**
|
||||||
* Updates a provided Permission entity based on this.
|
* Updates a provided Permission entity based on this.
|
||||||
*/
|
*/
|
||||||
public async update(permission: Permission): Promise<Permission> {
|
public async updatePermission(permission: Permission): Promise<Permission> {
|
||||||
permission.principal = await this.getPrincipal();
|
permission.principal = await this.getPrincipal();
|
||||||
permission.target = this.target;
|
permission.target = this.target;
|
||||||
permission.action = this.action;
|
permission.action = this.action;
|
||||||
@ -56,8 +57,12 @@ export class UpdatePermission {
|
|||||||
if (this.principal === undefined || this.principal === null) {
|
if (this.principal === undefined || this.principal === null) {
|
||||||
throw new PermissionNeedsPrincipalError();
|
throw new PermissionNeedsPrincipalError();
|
||||||
}
|
}
|
||||||
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal });
|
if (!isNaN(this.principal.id)) {
|
||||||
if (!principal) { throw new PrincipalNotFoundError(); }
|
let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal.id });
|
||||||
return principal;
|
if (!principal) { throw new PrincipalNotFoundError(); }
|
||||||
|
return principal;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PrincipalWrongTypeError();
|
||||||
}
|
}
|
||||||
}
|
}
|
59
src/models/actions/UpdateRunner.ts
Normal file
59
src/models/actions/UpdateRunner.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { IsInt, IsObject } from 'class-validator';
|
||||||
|
import { getConnectionManager } from 'typeorm';
|
||||||
|
import { RunnerGroupNotFoundError } from '../../errors/RunnerGroupErrors';
|
||||||
|
import { RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
|
||||||
|
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
|
||||||
|
import { Runner } from '../entities/Runner';
|
||||||
|
import { RunnerGroup } from '../entities/RunnerGroup';
|
||||||
|
import { CreateParticipant } from './CreateParticipant';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to update a Runner entity (via put request).
|
||||||
|
*/
|
||||||
|
export class UpdateRunner extends CreateParticipant {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updated runner's id.
|
||||||
|
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updated runner's new team/org.
|
||||||
|
* Just has to contain the group's id -everything else won't be checked or changed.
|
||||||
|
*/
|
||||||
|
@IsObject()
|
||||||
|
group: RunnerGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a provided Runner entity based on this.
|
||||||
|
*/
|
||||||
|
public async updateRunner(runner: Runner): Promise<Runner> {
|
||||||
|
runner.firstname = this.firstname;
|
||||||
|
runner.middlename = this.middlename;
|
||||||
|
runner.lastname = this.lastname;
|
||||||
|
runner.phone = this.phone;
|
||||||
|
runner.email = this.email;
|
||||||
|
runner.group = await this.getGroup();
|
||||||
|
runner.address = await this.getAddress();
|
||||||
|
|
||||||
|
return runner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the updated runner's group based on it's id.
|
||||||
|
*/
|
||||||
|
public async getGroup(): Promise<RunnerGroup> {
|
||||||
|
if (this.group === undefined || this.group === null) {
|
||||||
|
throw new RunnerTeamNeedsParentError();
|
||||||
|
}
|
||||||
|
if (!isNaN(this.group.id)) {
|
||||||
|
let group = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: this.group.id });
|
||||||
|
if (!group) { throw new RunnerGroupNotFoundError; }
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RunnerOrganisationWrongTypeError;
|
||||||
|
}
|
||||||
|
}
|
52
src/models/actions/UpdateRunnerOrganisation.ts
Normal file
52
src/models/actions/UpdateRunnerOrganisation.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { IsInt, IsOptional } from 'class-validator';
|
||||||
|
import { getConnectionManager } from 'typeorm';
|
||||||
|
import { AddressNotFoundError } from '../../errors/AddressErrors';
|
||||||
|
import { Address } from '../entities/Address';
|
||||||
|
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
|
||||||
|
import { CreateRunnerGroup } from './CreateRunnerGroup';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to update a RunnerOrganisation entity (via put request).
|
||||||
|
*/
|
||||||
|
export class UpdateRunnerOrganisation extends CreateRunnerGroup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updated orgs's id.
|
||||||
|
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updated organisation's address.
|
||||||
|
* Just has to contain the address's id - everything else won't be checked or changed.
|
||||||
|
* Optional.
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
address?: Address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the organisation's address based on it's id.
|
||||||
|
*/
|
||||||
|
public async getAddress(): Promise<Address> {
|
||||||
|
if (this.address === undefined || this.address === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address.id });
|
||||||
|
if (!address) { throw new AddressNotFoundError; }
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a provided RunnerOrganisation entity based on this.
|
||||||
|
*/
|
||||||
|
public async updateRunnerOrganisation(organisation: RunnerOrganisation): Promise<RunnerOrganisation> {
|
||||||
|
|
||||||
|
organisation.name = this.name;
|
||||||
|
organisation.contact = await this.getContact();
|
||||||
|
organisation.address = await this.getAddress();
|
||||||
|
|
||||||
|
return organisation;
|
||||||
|
}
|
||||||
|
}
|
56
src/models/actions/UpdateRunnerTeam.ts
Normal file
56
src/models/actions/UpdateRunnerTeam.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
|
||||||
|
import { getConnectionManager } from 'typeorm';
|
||||||
|
import { RunnerOrganisationNotFoundError, RunnerOrganisationWrongTypeError } from '../../errors/RunnerOrganisationErrors';
|
||||||
|
import { RunnerTeamNeedsParentError } from '../../errors/RunnerTeamErrors';
|
||||||
|
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
|
||||||
|
import { RunnerTeam } from '../entities/RunnerTeam';
|
||||||
|
import { CreateRunnerGroup } from './CreateRunnerGroup';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to update a RunnerTeam entity (via put request).
|
||||||
|
*/
|
||||||
|
export class UpdateRunnerTeam extends CreateRunnerGroup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updated team's id.
|
||||||
|
* This shouldn't have changed but it is here in case anyone ever wants to enable id changes (whyever they would want to).
|
||||||
|
*/
|
||||||
|
@IsInt()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The updated team's parentGroup.
|
||||||
|
* Just has to contain the organisation's id - everything else won't be checked or changed.
|
||||||
|
*/
|
||||||
|
@IsObject()
|
||||||
|
@IsNotEmpty()
|
||||||
|
parentGroup: RunnerOrganisation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the updated teams's parentGroup based on it's id.
|
||||||
|
*/
|
||||||
|
public async getParent(): Promise<RunnerOrganisation> {
|
||||||
|
if (this.parentGroup === undefined || this.parentGroup === null) {
|
||||||
|
throw new RunnerTeamNeedsParentError();
|
||||||
|
}
|
||||||
|
if (!isNaN(this.parentGroup.id)) {
|
||||||
|
let parentGroup = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: this.parentGroup.id });
|
||||||
|
if (!parentGroup) { throw new RunnerOrganisationNotFoundError();; }
|
||||||
|
return parentGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RunnerOrganisationWrongTypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a provided RunnerTeam entity based on this.
|
||||||
|
*/
|
||||||
|
public async updateRunnerTeam(team: RunnerTeam): Promise<RunnerTeam> {
|
||||||
|
|
||||||
|
team.name = this.name;
|
||||||
|
team.parentGroup = await this.getParent();
|
||||||
|
team.contact = await this.getContact()
|
||||||
|
|
||||||
|
return team;
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,11 @@
|
|||||||
import * as argon2 from "argon2";
|
import * as argon2 from "argon2";
|
||||||
import { passwordStrength } from "check-password-strength";
|
import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
||||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
|
|
||||||
import { getConnectionManager } from 'typeorm';
|
import { getConnectionManager } from 'typeorm';
|
||||||
import { config } from '../../../config';
|
import { config } from '../../config';
|
||||||
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
|
import { UsernameOrEmailNeededError } from '../../errors/AuthError';
|
||||||
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).
|
||||||
@ -42,7 +40,7 @@ export class UpdateUser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The updated user's username.
|
* The updated user's username.
|
||||||
* You have to provide a email addres, so this is optional.
|
* You have to provide at least one of: {email, username}.
|
||||||
*/
|
*/
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
@ -50,11 +48,12 @@ export class UpdateUser {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The updated user's email address.
|
* The updated user's email address.
|
||||||
|
* You have to provide at least one of: {email, username}.
|
||||||
*/
|
*/
|
||||||
@IsEmail()
|
@IsEmail()
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsOptional()
|
||||||
email: string;
|
email?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The updated user's phone number.
|
* The updated user's phone number.
|
||||||
@ -78,60 +77,42 @@ export class UpdateUser {
|
|||||||
* Should the user be enabled?
|
* Should the user be enabled?
|
||||||
*/
|
*/
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
|
||||||
enabled: boolean = true;
|
enabled: boolean = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The updated user's groups' ids.
|
* The updated user's groups.
|
||||||
|
* This just has to contain the group's id - everything else won't be changed.
|
||||||
*/
|
*/
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
groups?: number | number[]
|
groups?: UserGroup[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user's profile pic (or rather a url pointing to it).
|
* Updates a provided User entity based on this.
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsUrl()
|
|
||||||
@IsOptional()
|
|
||||||
profilePic?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates a user entity based on this.
|
|
||||||
* @param user The user that shall be updated.
|
|
||||||
*/
|
*/
|
||||||
public async update(user: User): Promise<User> {
|
public async updateUser(user: User): Promise<User> {
|
||||||
if (!this.email) {
|
user.email = this.email;
|
||||||
throw new UserEmailNeededError();
|
user.username = this.username;
|
||||||
|
if ((user.email === undefined || user.email === null) && (user.username === undefined || user.username === null)) {
|
||||||
|
throw new UsernameOrEmailNeededError();
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.email = this.email;
|
|
||||||
user.username = this.username;
|
|
||||||
user.enabled = this.enabled;
|
user.enabled = this.enabled;
|
||||||
user.firstname = this.firstname
|
user.firstname = this.firstname
|
||||||
user.middlename = this.middlename
|
user.middlename = this.middlename
|
||||||
user.lastname = this.lastname
|
user.lastname = this.lastname
|
||||||
user.phone = this.phone;
|
user.phone = this.phone;
|
||||||
user.groups = await this.getGroups();
|
user.groups = await this.getGroups();
|
||||||
|
//TODO: ProfilePics
|
||||||
if (!this.profilePic) { user.profilePic = `https://lauf-fuer-kaya.de/lfk-logo.png`; }
|
|
||||||
else { user.profilePic = this.profilePic; }
|
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get's all groups for this user by their id's;
|
* Loads the updated user's groups based on their ids.
|
||||||
*/
|
*/
|
||||||
public async getGroups() {
|
public async getGroups() {
|
||||||
if (!this.groups) { return null; }
|
if (!this.groups) { return null; }
|
||||||
@ -140,7 +121,7 @@ export class UpdateUser {
|
|||||||
this.groups = [this.groups]
|
this.groups = [this.groups]
|
||||||
}
|
}
|
||||||
for (let group of this.groups) {
|
for (let group of this.groups) {
|
||||||
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
|
let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group.id });
|
||||||
if (!found) { throw new UserGroupNotFoundError(); }
|
if (!found) { throw new UserGroupNotFoundError(); }
|
||||||
groups.push(found);
|
groups.push(found);
|
||||||
}
|
}
|
@ -1,53 +0,0 @@
|
|||||||
import { IsInt, IsPositive } from 'class-validator';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
|
|
||||||
import { DistanceDonation } from '../../entities/DistanceDonation';
|
|
||||||
import { Runner } from '../../entities/Runner';
|
|
||||||
import { CreateDonation } from './CreateDonation';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to create a new FixedDonation entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateDistanceDonation extends CreateDonation {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The donation's associated runner's id.
|
|
||||||
* This is important to link the runner's distance ran to the donation.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
runner: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The donation's amount per distance (full kilometer aka 1000 meters).
|
|
||||||
* The unit is your currency's smallest unit (default: euro cent).
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
amountPerDistance: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FixedDonation entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<DistanceDonation> {
|
|
||||||
let newDonation = new DistanceDonation;
|
|
||||||
|
|
||||||
newDonation.amountPerDistance = this.amountPerDistance;
|
|
||||||
newDonation.paidAmount = this.paidAmount;
|
|
||||||
newDonation.donor = await this.getDonor();
|
|
||||||
newDonation.runner = await this.getRunner();
|
|
||||||
|
|
||||||
return newDonation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a runner based on the runner id provided via this.runner.
|
|
||||||
*/
|
|
||||||
public async getRunner(): Promise<Runner> {
|
|
||||||
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
|
|
||||||
if (!runner) {
|
|
||||||
throw new RunnerNotFoundError();
|
|
||||||
}
|
|
||||||
return runner;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
import { IsInt, IsOptional, IsPositive } from 'class-validator';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import { DonorNotFoundError } from '../../../errors/DonorErrors';
|
|
||||||
import { Donation } from '../../entities/Donation';
|
|
||||||
import { Donor } from '../../entities/Donor';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to create a new Donation entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export abstract class CreateDonation {
|
|
||||||
/**
|
|
||||||
* The donation's associated donor's id.
|
|
||||||
* This is important to link donations to donors.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
donor: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
public abstract toEntity(): Promise<Donation>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a donor based on the donor id provided via this.donor.
|
|
||||||
*/
|
|
||||||
public async getDonor(): Promise<Donor> {
|
|
||||||
const donor = await getConnection().getRepository(Donor).findOne({ id: this.donor });
|
|
||||||
if (!donor) {
|
|
||||||
throw new DonorNotFoundError();
|
|
||||||
}
|
|
||||||
return donor;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
import { IsBoolean, IsOptional } from 'class-validator';
|
|
||||||
import { DonorReceiptAddressNeededError } from '../../../errors/DonorErrors';
|
|
||||||
import { Address } from '../../entities/Address';
|
|
||||||
import { Donor } from '../../entities/Donor';
|
|
||||||
import { CreateParticipant } from './CreateParticipant';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new Donor entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateDonor extends CreateParticipant {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does this donor need a receipt?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
@IsOptional()
|
|
||||||
receiptNeeded?: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Donor entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<Donor> {
|
|
||||||
let newDonor: Donor = new Donor();
|
|
||||||
|
|
||||||
newDonor.firstname = this.firstname;
|
|
||||||
newDonor.middlename = this.middlename;
|
|
||||||
newDonor.lastname = this.lastname;
|
|
||||||
newDonor.phone = this.phone;
|
|
||||||
newDonor.email = this.email;
|
|
||||||
newDonor.receiptNeeded = this.receiptNeeded;
|
|
||||||
newDonor.address = this.address;
|
|
||||||
Address.validate(newDonor.address);
|
|
||||||
if (this.receiptNeeded == true && Address.isValidAddress(newDonor.address) == false) {
|
|
||||||
throw new DonorReceiptAddressNeededError()
|
|
||||||
}
|
|
||||||
|
|
||||||
return newDonor;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import { IsInt, IsPositive } from 'class-validator';
|
|
||||||
import { FixedDonation } from '../../entities/FixedDonation';
|
|
||||||
import { CreateDonation } from './CreateDonation';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to create a new FixedDonation entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateFixedDonation extends CreateDonation {
|
|
||||||
/**
|
|
||||||
* The donation's amount.
|
|
||||||
* The unit is your currency's smallest unit (default: euro cent).
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
amount: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new FixedDonation entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<FixedDonation> {
|
|
||||||
let newDonation = new FixedDonation;
|
|
||||||
|
|
||||||
newDonation.amount = this.amount;
|
|
||||||
newDonation.paidAmount = this.paidAmount;
|
|
||||||
newDonation.donor = await this.getDonor();
|
|
||||||
|
|
||||||
return newDonation;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
|
||||||
import { getConnectionManager } from 'typeorm';
|
|
||||||
import { config } from '../../../config';
|
|
||||||
import { RunnerGroupNotFoundError } from '../../../errors/RunnerGroupErrors';
|
|
||||||
import { Address } from '../../entities/Address';
|
|
||||||
import { GroupContact } from '../../entities/GroupContact';
|
|
||||||
import { RunnerGroup } from '../../entities/RunnerGroup';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new GroupContact entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateGroupContact {
|
|
||||||
/**
|
|
||||||
* The new contact's first name.
|
|
||||||
*/
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
firstname: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new contact's middle name.
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
@IsString()
|
|
||||||
middlename?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new contact's last name.
|
|
||||||
*/
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
lastname: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new contact's address.
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
@IsObject()
|
|
||||||
address?: Address;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The contact's phone number.
|
|
||||||
* This will be validated against the configured country phone numer syntax (default: international).
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
@IsPhoneNumber(config.phone_validation_countrycode)
|
|
||||||
phone?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new contact's email address.
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
@IsEmail()
|
|
||||||
email?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new contacts's groups' ids.
|
|
||||||
* You can provide either one groupId or an array of groupIDs.
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
groups?: number[] | number
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get's all groups for this contact by their id's;
|
|
||||||
*/
|
|
||||||
public async getGroups(): Promise<RunnerGroup[]> {
|
|
||||||
if (!this.groups) { return null; }
|
|
||||||
let groups = new Array<RunnerGroup>();
|
|
||||||
if (!Array.isArray(this.groups)) {
|
|
||||||
this.groups = [this.groups]
|
|
||||||
}
|
|
||||||
for (let group of this.groups) {
|
|
||||||
let found = await getConnectionManager().get().getRepository(RunnerGroup).findOne({ id: group });
|
|
||||||
if (!found) { throw new RunnerGroupNotFoundError(); }
|
|
||||||
groups.push(found);
|
|
||||||
}
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new GroupContact entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<GroupContact> {
|
|
||||||
let newContact: GroupContact = new GroupContact();
|
|
||||||
newContact.firstname = this.firstname;
|
|
||||||
newContact.middlename = this.middlename;
|
|
||||||
newContact.lastname = this.lastname;
|
|
||||||
newContact.email = this.email;
|
|
||||||
newContact.phone = this.phone;
|
|
||||||
newContact.address = this.address;
|
|
||||||
Address.validate(newContact.address);
|
|
||||||
newContact.groups = await this.getGroups();
|
|
||||||
|
|
||||||
return newContact;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
import { IsEmail, IsNotEmpty, IsObject, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
|
|
||||||
import { config } from '../../../config';
|
|
||||||
import { Address } from '../../entities/Address';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new Participant entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export abstract class CreateParticipant {
|
|
||||||
/**
|
|
||||||
* The new participant's first name.
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
firstname: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new participant's middle name.
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
middlename?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new participant's last name.
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
lastname: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new participant's phone number.
|
|
||||||
* This will be validated against the configured country phone numer syntax (default: international).
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@IsPhoneNumber(config.phone_validation_countrycode)
|
|
||||||
phone?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new participant's e-mail address.
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
@IsEmail()
|
|
||||||
email?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new participant's address.
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
@IsObject()
|
|
||||||
address?: Address;
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import { IsBoolean, IsInt, IsOptional } from 'class-validator';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
|
|
||||||
import { Runner } from '../../entities/Runner';
|
|
||||||
import { RunnerCard } from '../../entities/RunnerCard';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new RunnerCard entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateRunnerCard {
|
|
||||||
/**
|
|
||||||
* The card's associated runner's id.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsOptional()
|
|
||||||
runner?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the new card enabled (for fraud reasons)?
|
|
||||||
* Default: true
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
enabled: boolean = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new RunnerCard entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<RunnerCard> {
|
|
||||||
let newCard: RunnerCard = new RunnerCard();
|
|
||||||
|
|
||||||
newCard.enabled = this.enabled;
|
|
||||||
newCard.runner = await this.getRunner();
|
|
||||||
|
|
||||||
return newCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRunner(): Promise<Runner> {
|
|
||||||
if (!this.runner) { return null; }
|
|
||||||
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
|
|
||||||
if (!runner) {
|
|
||||||
throw new RunnerNotFoundError();
|
|
||||||
}
|
|
||||||
return runner;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +0,0 @@
|
|||||||
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
|
||||||
import { getConnectionManager } from 'typeorm';
|
|
||||||
import { GroupContactNotFoundError } from '../../../errors/GroupContactErrors';
|
|
||||||
import { GroupContact } from '../../entities/GroupContact';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new RunnerGroup entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export abstract class CreateRunnerGroup {
|
|
||||||
/**
|
|
||||||
* The new group's name.
|
|
||||||
*/
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsString()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new group's contact's id.
|
|
||||||
* Optional
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsOptional()
|
|
||||||
contact?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the new group's contact by it's id.
|
|
||||||
*/
|
|
||||||
public async getContact(): Promise<GroupContact> {
|
|
||||||
if (!this.contact) { return null; }
|
|
||||||
let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
|
|
||||||
if (!contact) { throw new GroupContactNotFoundError; }
|
|
||||||
return contact;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
import { IsBoolean, IsObject, IsOptional } from 'class-validator';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
import { Address } from '../../entities/Address';
|
|
||||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
|
|
||||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new RunnerOrganization entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateRunnerOrganization extends CreateRunnerGroup {
|
|
||||||
/**
|
|
||||||
* The new organization's address.
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
@IsObject()
|
|
||||||
address?: Address;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is registration enabled for the new organization?
|
|
||||||
*/
|
|
||||||
@IsOptional()
|
|
||||||
@IsBoolean()
|
|
||||||
registrationEnabled?: boolean = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new RunnerOrganization entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<RunnerOrganization> {
|
|
||||||
let newRunnerOrganization: RunnerOrganization = new RunnerOrganization();
|
|
||||||
|
|
||||||
newRunnerOrganization.name = this.name;
|
|
||||||
newRunnerOrganization.contact = await this.getContact();
|
|
||||||
newRunnerOrganization.address = this.address;
|
|
||||||
Address.validate(newRunnerOrganization.address);
|
|
||||||
|
|
||||||
if (this.registrationEnabled) {
|
|
||||||
newRunnerOrganization.key = uuid.v4().toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
return newRunnerOrganization;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
import { IsBoolean, IsInt, IsOptional, IsPositive } from 'class-validator';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
|
|
||||||
import { Runner } from '../../entities/Runner';
|
|
||||||
import { Scan } from '../../entities/Scan';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to create a new Scan entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export abstract class CreateScan {
|
|
||||||
/**
|
|
||||||
* The scan's associated runner's id.
|
|
||||||
* This is important to link ran distances to runners.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
runner: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the scan valid (for fraud reasons).
|
|
||||||
* The determination of validity will work differently for every child class.
|
|
||||||
* Default: true
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
@IsOptional()
|
|
||||||
valid?: boolean = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The scan's distance in meters.
|
|
||||||
* Can be set manually or derived from another object.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
public distance: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Scan entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<Scan> {
|
|
||||||
let newScan = new Scan();
|
|
||||||
|
|
||||||
newScan.distance = this.distance;
|
|
||||||
newScan.valid = this.valid;
|
|
||||||
newScan.runner = await this.getRunner();
|
|
||||||
|
|
||||||
return newScan;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a runner based on the runner id provided via this.runner.
|
|
||||||
*/
|
|
||||||
public async getRunner(): Promise<Runner> {
|
|
||||||
const runner = await getConnection().getRepository(Runner).findOne({ id: this.runner });
|
|
||||||
if (!runner) {
|
|
||||||
throw new RunnerNotFoundError();
|
|
||||||
}
|
|
||||||
return runner;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
import * as argon2 from "argon2";
|
|
||||||
import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator';
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import * as uuid from 'uuid';
|
|
||||||
import { TrackNotFoundError } from '../../../errors/TrackErrors';
|
|
||||||
import { ScanStation } from '../../entities/ScanStation';
|
|
||||||
import { Track } from '../../entities/Track';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to create a new StatsClient entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateScanStation {
|
|
||||||
/**
|
|
||||||
* The new station's description.
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsOptional()
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The station's associated track's id.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
track: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is this station enabled?
|
|
||||||
*/
|
|
||||||
@IsBoolean()
|
|
||||||
@IsOptional()
|
|
||||||
enabled?: boolean = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts this to a ScanStation entity.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<ScanStation> {
|
|
||||||
let newStation: ScanStation = new ScanStation();
|
|
||||||
|
|
||||||
newStation.description = this.description;
|
|
||||||
newStation.enabled = this.enabled;
|
|
||||||
newStation.track = await this.getTrack();
|
|
||||||
|
|
||||||
let newUUID = uuid.v4().toUpperCase();
|
|
||||||
newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase();
|
|
||||||
newStation.key = await argon2.hash(newStation.prefix + "." + newUUID);
|
|
||||||
newStation.cleartextkey = newStation.prefix + "." + newUUID;
|
|
||||||
|
|
||||||
return newStation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get's a track by it's id provided via this.track.
|
|
||||||
* Used to link the new station to a track.
|
|
||||||
*/
|
|
||||||
public async getTrack(): Promise<Track> {
|
|
||||||
const track = await getConnection().getRepository(Track).findOne({ id: this.track });
|
|
||||||
if (!track) {
|
|
||||||
throw new TrackNotFoundError();
|
|
||||||
}
|
|
||||||
return track;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import { RunnerEmailNeededError } from '../../../errors/RunnerErrors';
|
|
||||||
import { Address } from '../../entities/Address';
|
|
||||||
import { Runner } from '../../entities/Runner';
|
|
||||||
import { RunnerOrganization } from '../../entities/RunnerOrganization';
|
|
||||||
import { CreateParticipant } from './CreateParticipant';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new Runner entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateSelfServiceCitizenRunner extends CreateParticipant {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new runners's e-mail address.
|
|
||||||
* Must be provided for email-verification to work.
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsEmail()
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Runner entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<Runner> {
|
|
||||||
let newRunner: Runner = new Runner();
|
|
||||||
|
|
||||||
newRunner.firstname = this.firstname;
|
|
||||||
newRunner.middlename = this.middlename;
|
|
||||||
newRunner.lastname = this.lastname;
|
|
||||||
newRunner.phone = this.phone;
|
|
||||||
newRunner.email = this.email;
|
|
||||||
|
|
||||||
if (!newRunner.email) {
|
|
||||||
throw new RunnerEmailNeededError();
|
|
||||||
}
|
|
||||||
|
|
||||||
newRunner.group = await this.getGroup();
|
|
||||||
newRunner.address = this.address;
|
|
||||||
Address.validate(newRunner.address);
|
|
||||||
|
|
||||||
return newRunner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the new runner's group by it's id.
|
|
||||||
*/
|
|
||||||
public async getGroup(): Promise<RunnerOrganization> {
|
|
||||||
return await getConnection().getRepository(RunnerOrganization).findOne({ id: 1 });
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
import { IsInt, IsOptional } from 'class-validator';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import { RunnerTeamNotFoundError } from '../../../errors/RunnerTeamErrors';
|
|
||||||
import { Address } from '../../entities/Address';
|
|
||||||
import { Runner } from '../../entities/Runner';
|
|
||||||
import { RunnerGroup } from '../../entities/RunnerGroup';
|
|
||||||
import { RunnerTeam } from '../../entities/RunnerTeam';
|
|
||||||
import { CreateParticipant } from './CreateParticipant';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new Runner entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateSelfServiceRunner extends CreateParticipant {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new runner's team's id.
|
|
||||||
* The team has to be a part of the runner's org.
|
|
||||||
* The team property may get ignored.
|
|
||||||
* If no team get's provided the runner's group will be their org.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsOptional()
|
|
||||||
team?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Runner entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(group: RunnerGroup): Promise<Runner> {
|
|
||||||
let newRunner: Runner = new Runner();
|
|
||||||
|
|
||||||
newRunner.firstname = this.firstname;
|
|
||||||
newRunner.middlename = this.middlename;
|
|
||||||
newRunner.lastname = this.lastname;
|
|
||||||
newRunner.phone = this.phone;
|
|
||||||
newRunner.email = this.email;
|
|
||||||
newRunner.group = await this.getGroup(group);
|
|
||||||
newRunner.address = this.address;
|
|
||||||
Address.validate(newRunner.address);
|
|
||||||
|
|
||||||
return newRunner;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the new runner's group by it's id.
|
|
||||||
*/
|
|
||||||
public async getGroup(group: RunnerGroup): Promise<RunnerGroup> {
|
|
||||||
if (!this.team) {
|
|
||||||
return group;
|
|
||||||
}
|
|
||||||
const team = await getConnection().getRepository(RunnerTeam).findOne({ id: this.team }, { relations: ["parentGroup"] });
|
|
||||||
if (!team) { throw new RunnerTeamNotFoundError(); }
|
|
||||||
if (team.parentGroup.id != group.id) { throw new RunnerTeamNotFoundError(); }
|
|
||||||
return team;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
import { IsInt, IsNotEmpty, IsOptional, IsPositive, IsString } from 'class-validator';
|
|
||||||
import { TrackLapTimeCantBeNegativeError } from '../../../errors/TrackErrors';
|
|
||||||
import { Track } from '../../entities/Track';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new Track entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateTrack {
|
|
||||||
/**
|
|
||||||
* The new track's name.
|
|
||||||
*/
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The new track's distance in meters (must be greater than 0).
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
distance: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The minimum time a runner should take to run a lap on this track (in seconds).
|
|
||||||
* Will be used for fraud detection.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsOptional()
|
|
||||||
minimumLapTime: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Track entity from this.
|
|
||||||
*/
|
|
||||||
public toEntity(): Track {
|
|
||||||
let newTrack: Track = new Track();
|
|
||||||
|
|
||||||
newTrack.name = this.name;
|
|
||||||
newTrack.distance = this.distance;
|
|
||||||
newTrack.minimumLapTime = this.minimumLapTime;
|
|
||||||
if (this.minimumLapTime < 0) {
|
|
||||||
throw new TrackLapTimeCantBeNegativeError();
|
|
||||||
}
|
|
||||||
|
|
||||||
return newTrack;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
import { IsInt, IsOptional, IsPositive } from 'class-validator';
|
|
||||||
import { BadRequestError } from 'routing-controllers';
|
|
||||||
import { getConnection } from 'typeorm';
|
|
||||||
import { RunnerCardNotFoundError } from '../../../errors/RunnerCardErrors';
|
|
||||||
import { RunnerNotFoundError } from '../../../errors/RunnerErrors';
|
|
||||||
import { ScanStationNotFoundError } from '../../../errors/ScanStationErrors';
|
|
||||||
import { RunnerCard } from '../../entities/RunnerCard';
|
|
||||||
import { ScanStation } from '../../entities/ScanStation';
|
|
||||||
import { TrackScan } from '../../entities/TrackScan';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This classed is used to create a new Scan entity from a json body (post request).
|
|
||||||
*/
|
|
||||||
export class CreateTrackScan {
|
|
||||||
/**
|
|
||||||
* The id of the runnerCard associated with the scan.
|
|
||||||
* This get's saved for documentation and management purposes.
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
card: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The scanning station's id that created the scan.
|
|
||||||
* Mainly used for logging and traceing back scans (or errors).
|
|
||||||
* You don't have to provide the station if you're authenticateing via a scanstation token (The server takes care of it for you).
|
|
||||||
*/
|
|
||||||
@IsInt()
|
|
||||||
@IsPositive()
|
|
||||||
@IsOptional()
|
|
||||||
station?: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Track entity from this.
|
|
||||||
*/
|
|
||||||
public async toEntity(): Promise<TrackScan> {
|
|
||||||
let newScan: TrackScan = new TrackScan();
|
|
||||||
|
|
||||||
newScan.station = await this.getStation();
|
|
||||||
newScan.card = await this.getCard();
|
|
||||||
|
|
||||||
newScan.track = newScan.station.track;
|
|
||||||
newScan.runner = newScan.card.runner;
|
|
||||||
|
|
||||||
if (!newScan.runner) {
|
|
||||||
throw new RunnerNotFoundError();
|
|
||||||
}
|
|
||||||
|
|
||||||
newScan.timestamp = Math.round(new Date().getTime() / 1000);
|
|
||||||
newScan = await this.validateScan(newScan);
|
|
||||||
|
|
||||||
return newScan;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get's a runnerCard entity via the provided id.
|
|
||||||
* @returns The runnerCard whom's id you provided.
|
|
||||||
*/
|
|
||||||
public async getCard(): Promise<RunnerCard> {
|
|
||||||
const id = this.card % 200000000000;
|
|
||||||
const runnerCard = await getConnection().getRepository(RunnerCard).findOne({ id: id }, { relations: ["runner"] });
|
|
||||||
if (!runnerCard) {
|
|
||||||
throw new RunnerCardNotFoundError();
|
|
||||||
}
|
|
||||||
return runnerCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get's a scanstation entity via the provided id.
|
|
||||||
* @returns The scanstation whom's id you provided.
|
|
||||||
*/
|
|
||||||
public async getStation(): Promise<ScanStation> {
|
|
||||||
if (!this.station) {
|
|
||||||
throw new BadRequestError("You are missing the station's id!")
|
|
||||||
}
|
|
||||||
const station = await getConnection().getRepository(ScanStation).findOne({ id: this.station }, { relations: ["track"] });
|
|
||||||
if (!station) {
|
|
||||||
throw new ScanStationNotFoundError();
|
|
||||||
}
|
|
||||||
return station;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the scan and sets it's lap time;
|
|
||||||
* @param scan The scan you want to validate
|
|
||||||
* @returns The validated scan with it's laptime set.
|
|
||||||
*/
|
|
||||||
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' } });
|
|
||||||
if (!latestScan) {
|
|
||||||
scan.lapTime = 0;
|
|
||||||
scan.valid = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
scan.lapTime = scan.timestamp - latestScan.timestamp;
|
|
||||||
scan.valid = (scan.lapTime > scan.track.minimumLapTime);
|
|
||||||
}
|
|
||||||
return scan;
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user