Merge alpha 0.0.5 to master #54
							
								
								
									
										123
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,123 @@
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
name: tests:node_latest
 | 
			
		||||
clone:
 | 
			
		||||
  disable: true
 | 
			
		||||
steps:
 | 
			
		||||
  - name: checkout pr
 | 
			
		||||
    image: alpine/git
 | 
			
		||||
    commands:
 | 
			
		||||
      - git clone $DRONE_REMOTE_URL .
 | 
			
		||||
      - git checkout $DRONE_SOURCE_BRANCH
 | 
			
		||||
      - mv .env.ci .env
 | 
			
		||||
  - name: run tests
 | 
			
		||||
    image: node:alpine
 | 
			
		||||
    commands:
 | 
			
		||||
      - yarn
 | 
			
		||||
      - yarn test:ci
 | 
			
		||||
trigger:
 | 
			
		||||
  event:
 | 
			
		||||
    - pull_request
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
name: build:dev
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: build dev
 | 
			
		||||
    image: plugins/docker
 | 
			
		||||
    depends_on: [clone]
 | 
			
		||||
    settings:
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: DOCKER_REGISTRY_USER
 | 
			
		||||
      password:
 | 
			
		||||
        from_secret: DOCKER_REGISTRY_PASSWORD
 | 
			
		||||
      repo: registry.odit.services/lfk/backend
 | 
			
		||||
      tags:
 | 
			
		||||
        - dev
 | 
			
		||||
      registry: registry.odit.services
 | 
			
		||||
    when:
 | 
			
		||||
      branch:
 | 
			
		||||
        - dev
 | 
			
		||||
      event:
 | 
			
		||||
        - push
 | 
			
		||||
 | 
			
		||||
trigger:
 | 
			
		||||
  branch:
 | 
			
		||||
    - dev
 | 
			
		||||
  event:
 | 
			
		||||
    - push
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
name: build:latest
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: build latest
 | 
			
		||||
    image: plugins/docker
 | 
			
		||||
    depends_on: [clone]
 | 
			
		||||
    settings:
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: DOCKER_REGISTRY_USER
 | 
			
		||||
      password:
 | 
			
		||||
        from_secret: DOCKER_REGISTRY_PASSWORD
 | 
			
		||||
      repo: registry.odit.services/lfk/backend
 | 
			
		||||
      tags:
 | 
			
		||||
        - latest
 | 
			
		||||
      registry: registry.odit.services
 | 
			
		||||
 | 
			
		||||
trigger:
 | 
			
		||||
  branch:
 | 
			
		||||
    - main
 | 
			
		||||
  event:
 | 
			
		||||
    - push
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
kind: pipeline
 | 
			
		||||
type: docker
 | 
			
		||||
name: build:tags
 | 
			
		||||
 | 
			
		||||
steps:
 | 
			
		||||
  - name: build $DRONE_TAG
 | 
			
		||||
    image: plugins/docker
 | 
			
		||||
    depends_on: [clone]
 | 
			
		||||
    settings:
 | 
			
		||||
      username:
 | 
			
		||||
        from_secret: DOCKER_REGISTRY_USER
 | 
			
		||||
      password:
 | 
			
		||||
        from_secret: DOCKER_REGISTRY_PASSWORD
 | 
			
		||||
      repo: registry.odit.services/lfk/backend
 | 
			
		||||
      tags:
 | 
			
		||||
        - $DRONE_TAG
 | 
			
		||||
      registry: registry.odit.services
 | 
			
		||||
  - name: trigger js lib build
 | 
			
		||||
    depends_on: [clone]
 | 
			
		||||
    image: plugins/downstream
 | 
			
		||||
    settings:
 | 
			
		||||
      server: https://ci.odit.services/
 | 
			
		||||
      token: 
 | 
			
		||||
        from_secret: BOT_DRONE_KEY
 | 
			
		||||
      fork: false
 | 
			
		||||
      repositories:
 | 
			
		||||
        - lfk/lfk-client-js
 | 
			
		||||
      params:
 | 
			
		||||
      - SOURCE_TAG: $DRONE_TAG
 | 
			
		||||
  - name: trigger node lib build
 | 
			
		||||
    depends_on: [clone]
 | 
			
		||||
    image: plugins/downstream
 | 
			
		||||
    settings:
 | 
			
		||||
      server: https://ci.odit.services/
 | 
			
		||||
      token: 
 | 
			
		||||
        from_secret: BOT_DRONE_KEY
 | 
			
		||||
      fork: false
 | 
			
		||||
      repositories:
 | 
			
		||||
        - lfk/lfk-client-node
 | 
			
		||||
      params:
 | 
			
		||||
      - SOURCE_TAG: $DRONE_TAG
 | 
			
		||||
trigger:
 | 
			
		||||
  branch:
 | 
			
		||||
    - main
 | 
			
		||||
  event:
 | 
			
		||||
    - tag
 | 
			
		||||
							
								
								
									
										9
									
								
								.env.ci
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.env.ci
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
APP_PORT=4010
 | 
			
		||||
DB_TYPE=sqlite
 | 
			
		||||
DB_HOST=unused
 | 
			
		||||
DB_PORT=unused
 | 
			
		||||
DB_USER=unused
 | 
			
		||||
DB_PASSWORD=bla
 | 
			
		||||
DB_NAME=./test.sqlite
 | 
			
		||||
NODE_ENV=dev
 | 
			
		||||
POSTALCODE_COUNTRYCODE=null
 | 
			
		||||
@@ -5,4 +5,5 @@ DB_PORT=bla
 | 
			
		||||
DB_USER=bla
 | 
			
		||||
DB_PASSWORD=bla
 | 
			
		||||
DB_NAME=bla
 | 
			
		||||
NODE_ENV=production
 | 
			
		||||
NODE_ENV=production
 | 
			
		||||
POSTALCODE_COUNTRYCODE=null
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -131,4 +131,6 @@ package-lock.json
 | 
			
		||||
build
 | 
			
		||||
 | 
			
		||||
*.sqlite
 | 
			
		||||
docs
 | 
			
		||||
*.sqlite-jurnal
 | 
			
		||||
/docs
 | 
			
		||||
lib
 | 
			
		||||
							
								
								
									
										20
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,6 +1,16 @@
 | 
			
		||||
FROM node:alpine
 | 
			
		||||
# Typescript Build
 | 
			
		||||
FROM node:14.15.1-alpine3.12
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY ./package.json ./
 | 
			
		||||
RUN yarn
 | 
			
		||||
COPY ./ ./
 | 
			
		||||
ENTRYPOINT [ "yarn","dev" ]
 | 
			
		||||
COPY package.json ./
 | 
			
		||||
RUN npm i -g pnpm
 | 
			
		||||
RUN pnpm i
 | 
			
		||||
COPY tsconfig.json ormconfig.js ./
 | 
			
		||||
COPY src ./src
 | 
			
		||||
RUN pnpm run build
 | 
			
		||||
# final image
 | 
			
		||||
FROM node:14.15.1-alpine3.12
 | 
			
		||||
COPY package.json ormconfig.js ./
 | 
			
		||||
RUN npm i -g pnpm
 | 
			
		||||
RUN pnpm i --prod
 | 
			
		||||
COPY --from=0 /app/dist dist
 | 
			
		||||
ENTRYPOINT ["node", "dist/app.js"]
 | 
			
		||||
@@ -6,18 +6,26 @@ services:
 | 
			
		||||
      - 4010:4010
 | 
			
		||||
    environment:
 | 
			
		||||
      APP_PORT: 4010
 | 
			
		||||
      DB_TYPE: postgres
 | 
			
		||||
      DB_HOST: backend_db
 | 
			
		||||
      DB_PORT: 5432 
 | 
			
		||||
      DB_USER: lfk
 | 
			
		||||
      DB_PASSWORD: changeme
 | 
			
		||||
      DB_NAME: lfk
 | 
			
		||||
      DB_TYPE: sqlite
 | 
			
		||||
      DB_HOST: bla
 | 
			
		||||
      DB_PORT: bla
 | 
			
		||||
      DB_USER: bla
 | 
			
		||||
      DB_PASSWORD: bla
 | 
			
		||||
      DB_NAME: dev.sqlite
 | 
			
		||||
      NODE_ENV: production
 | 
			
		||||
  backend_db:
 | 
			
		||||
    image: postgres:11-alpine
 | 
			
		||||
    environment:
 | 
			
		||||
      POSTGRES_DB: lfk
 | 
			
		||||
      POSTGRES_PASSWORD: changeme
 | 
			
		||||
      POSTGRES_USER: lfk
 | 
			
		||||
    ports:
 | 
			
		||||
      - 5432:5432
 | 
			
		||||
      # APP_PORT: 4010
 | 
			
		||||
      # DB_TYPE: postgres
 | 
			
		||||
      # DB_HOST: backend_db
 | 
			
		||||
      # DB_PORT: 5432
 | 
			
		||||
      # DB_USER: lfk
 | 
			
		||||
      # DB_PASSWORD: changeme
 | 
			
		||||
      # DB_NAME: lfk
 | 
			
		||||
      # NODE_ENV: production
 | 
			
		||||
  # backend_db:
 | 
			
		||||
  #   image: postgres:11-alpine
 | 
			
		||||
  #   environment:
 | 
			
		||||
  #     POSTGRES_DB: lfk
 | 
			
		||||
  #     POSTGRES_PASSWORD: changeme
 | 
			
		||||
  #     POSTGRES_USER: lfk
 | 
			
		||||
  #   ports:
 | 
			
		||||
  #     - 5432:5432
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								jest.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								jest.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  preset: 'ts-jest',
 | 
			
		||||
  testEnvironment: 'node',
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										16
									
								
								ormconfig.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								ormconfig.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
const dotenv = require('dotenv');
 | 
			
		||||
dotenv.config();
 | 
			
		||||
//
 | 
			
		||||
const SOURCE_PATH = process.env.NODE_ENV === 'production' ? 'dist' : 'src';
 | 
			
		||||
module.exports = {
 | 
			
		||||
	type: process.env.DB_TYPE,
 | 
			
		||||
	host: process.env.DB_HOST,
 | 
			
		||||
	port: process.env.DB_PORT,
 | 
			
		||||
	username: process.env.DB_USER,
 | 
			
		||||
	password: process.env.DB_PASSWORD,
 | 
			
		||||
	database: process.env.DB_NAME,
 | 
			
		||||
	// entities: ["src/**/entities/*.ts"],
 | 
			
		||||
	entities: [ `${SOURCE_PATH}/**/entities/*{.ts,.js}` ],
 | 
			
		||||
	seeds: [ `${SOURCE_PATH}/**/seeds/*{.ts,.js}` ]
 | 
			
		||||
	// seeds: ['src/seeds/*.ts'],
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										12
									
								
								ormconfig.ts
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								ormconfig.ts
									
									
									
									
									
								
							@@ -1,12 +0,0 @@
 | 
			
		||||
import { config } from 'dotenv';
 | 
			
		||||
config();
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
	type: process.env.DB_TYPE,
 | 
			
		||||
	host: process.env.DB_HOST,
 | 
			
		||||
	port: process.env.DB_PORT,
 | 
			
		||||
	username: process.env.DB_USER,
 | 
			
		||||
	password: process.env.DB_PASSWORD,
 | 
			
		||||
	database: process.env.DB_NAME,
 | 
			
		||||
	entities: ["src/models/entities/*.ts"]
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										150
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										150
									
								
								package.json
									
									
									
									
									
								
							@@ -1,67 +1,85 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "@lfk/backend",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "main": "src/app.ts",
 | 
			
		||||
  "repository": "https://git.odit.services/lfk/backend",
 | 
			
		||||
  "author": {
 | 
			
		||||
    "name": "ODIT.Services",
 | 
			
		||||
    "email": "info@odit.services",
 | 
			
		||||
    "url": "https://odit.services"
 | 
			
		||||
  },
 | 
			
		||||
  "contributors": [
 | 
			
		||||
    {
 | 
			
		||||
      "name": "Philipp Dormann",
 | 
			
		||||
      "email": "philipp@philippdormann.de",
 | 
			
		||||
      "url": "https://philippdormann.de"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "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",
 | 
			
		||||
    "body-parser": "^1.19.0",
 | 
			
		||||
    "class-transformer": "^0.3.1",
 | 
			
		||||
    "class-validator": "^0.12.2",
 | 
			
		||||
    "class-validator-jsonschema": "^2.0.3",
 | 
			
		||||
    "consola": "^2.15.0",
 | 
			
		||||
    "cors": "^2.8.5",
 | 
			
		||||
    "dotenv": "^8.2.0",
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "helmet": "^4.2.0",
 | 
			
		||||
    "jsonwebtoken": "^8.5.1",
 | 
			
		||||
    "multer": "^1.4.2",
 | 
			
		||||
    "mysql": "^2.18.1",
 | 
			
		||||
    "pg": "^8.5.1",
 | 
			
		||||
    "reflect-metadata": "^0.1.13",
 | 
			
		||||
    "routing-controllers": "^0.9.0-alpha.6",
 | 
			
		||||
    "routing-controllers-openapi": "^2.1.0",
 | 
			
		||||
    "swagger-ui-express": "^4.1.5",
 | 
			
		||||
    "typeorm": "^0.2.29",
 | 
			
		||||
    "typeorm-routing-controllers-extensions": "^0.2.0",
 | 
			
		||||
    "uuid": "^8.3.1"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/cors": "^2.8.8",
 | 
			
		||||
    "@types/dotenv": "^8.2.0",
 | 
			
		||||
    "@types/express": "^4.17.9",
 | 
			
		||||
    "@types/jsonwebtoken": "^8.5.0",
 | 
			
		||||
    "@types/multer": "^1.4.4",
 | 
			
		||||
    "@types/node": "^14.14.9",
 | 
			
		||||
    "@types/swagger-ui-express": "^4.1.2",
 | 
			
		||||
    "@types/uuid": "^8.3.0",
 | 
			
		||||
    "nodemon": "^2.0.6",
 | 
			
		||||
    "sqlite3": "^5.0.0",
 | 
			
		||||
    "ts-node": "^9.0.0",
 | 
			
		||||
    "typedoc": "^0.19.2",
 | 
			
		||||
    "typescript": "^4.1.2"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "nodemon src/app.ts",
 | 
			
		||||
    "build": "tsc",
 | 
			
		||||
    "docs": "typedoc --out docs src"
 | 
			
		||||
  }
 | 
			
		||||
{
 | 
			
		||||
  "name": "@odit/lfk-backend",
 | 
			
		||||
  "version": "0.0.5",
 | 
			
		||||
  "main": "src/app.ts",
 | 
			
		||||
  "repository": "https://git.odit.services/lfk/backend",
 | 
			
		||||
  "author": {
 | 
			
		||||
    "name": "ODIT.Services",
 | 
			
		||||
    "email": "info@odit.services",
 | 
			
		||||
    "url": "https://odit.services"
 | 
			
		||||
  },
 | 
			
		||||
  "contributors": [
 | 
			
		||||
    {
 | 
			
		||||
      "name": "Philipp Dormann",
 | 
			
		||||
      "email": "philipp@philippdormann.de",
 | 
			
		||||
      "url": "https://philippdormann.de"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "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",
 | 
			
		||||
    "body-parser": "^1.19.0",
 | 
			
		||||
    "class-transformer": "^0.3.1",
 | 
			
		||||
    "class-validator": "^0.12.2",
 | 
			
		||||
    "class-validator-jsonschema": "^2.0.3",
 | 
			
		||||
    "consola": "^2.15.0",
 | 
			
		||||
    "cookie": "^0.4.1",
 | 
			
		||||
    "cookie-parser": "^1.4.5",
 | 
			
		||||
    "cors": "^2.8.5",
 | 
			
		||||
    "csvtojson": "^2.0.10",
 | 
			
		||||
    "dotenv": "^8.2.0",
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "jsonwebtoken": "^8.5.1",
 | 
			
		||||
    "mysql": "^2.18.1",
 | 
			
		||||
    "pg": "^8.5.1",
 | 
			
		||||
    "reflect-metadata": "^0.1.13",
 | 
			
		||||
    "routing-controllers": "^0.9.0-alpha.6",
 | 
			
		||||
    "routing-controllers-openapi": "^2.1.0",
 | 
			
		||||
    "sqlite3": "^5.0.0",
 | 
			
		||||
    "typeorm": "^0.2.29",
 | 
			
		||||
    "typeorm-routing-controllers-extensions": "^0.2.0",
 | 
			
		||||
    "typeorm-seeding": "^1.6.1",
 | 
			
		||||
    "uuid": "^8.3.1",
 | 
			
		||||
    "validator": "^13.5.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/cors": "^2.8.8",
 | 
			
		||||
    "@types/csvtojson": "^1.1.5",
 | 
			
		||||
    "@types/express": "^4.17.9",
 | 
			
		||||
    "@types/jest": "^26.0.16",
 | 
			
		||||
    "@types/jsonwebtoken": "^8.5.0",
 | 
			
		||||
    "@types/node": "^14.14.9",
 | 
			
		||||
    "@types/uuid": "^8.3.0",
 | 
			
		||||
    "axios": "^0.21.0",
 | 
			
		||||
    "cp-cli": "^2.0.0",
 | 
			
		||||
    "jest": "^26.6.3",
 | 
			
		||||
    "nodemon": "^2.0.6",
 | 
			
		||||
    "rimraf": "^2.7.1",
 | 
			
		||||
    "start-server-and-test": "^1.11.6",
 | 
			
		||||
    "ts-jest": "^26.4.4",
 | 
			
		||||
    "ts-node": "^9.0.0",
 | 
			
		||||
    "typedoc": "^0.19.2",
 | 
			
		||||
    "typescript": "^4.1.2"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "nodemon src/app.ts",
 | 
			
		||||
    "build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static",
 | 
			
		||||
    "docs": "typedoc --out docs src",
 | 
			
		||||
    "test": "jest",
 | 
			
		||||
    "test:watch": "jest --watchAll",
 | 
			
		||||
    "test:ci": "start-server-and-test dev http://localhost:4010/api/docs/openapi.json test",
 | 
			
		||||
    "seed": "ts-node ./node_modules/typeorm/cli.js schema:sync && ts-node ./node_modules/typeorm-seeding/dist/cli.js seed",
 | 
			
		||||
    "openapi:export": "ts-node src/openapi_export.ts"
 | 
			
		||||
  },
 | 
			
		||||
  "nodemonConfig": {
 | 
			
		||||
    "ignore": [
 | 
			
		||||
      "src/tests/*",
 | 
			
		||||
      "docs/*"
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,13 +6,14 @@ import { config, e as errors } from './config';
 | 
			
		||||
import loaders from "./loaders/index";
 | 
			
		||||
import { ErrorHandler } from './middlewares/ErrorHandler';
 | 
			
		||||
 | 
			
		||||
const CONTROLLERS_FILE_EXTENSION = process.env.NODE_ENV === 'production' ? 'js' : 'ts';
 | 
			
		||||
const app = createExpressServer({
 | 
			
		||||
  authorizationChecker: authchecker,
 | 
			
		||||
  middlewares: [ErrorHandler],
 | 
			
		||||
  development: config.development,
 | 
			
		||||
  cors: true,
 | 
			
		||||
  routePrefix: "/api",
 | 
			
		||||
  controllers: [__dirname + "/controllers/*.ts"],
 | 
			
		||||
  controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
async function main() {
 | 
			
		||||
@@ -26,6 +27,6 @@ async function main() {
 | 
			
		||||
if (errors === 0) {
 | 
			
		||||
  main();
 | 
			
		||||
} else {
 | 
			
		||||
  console.log("error");
 | 
			
		||||
  consola.error("error");
 | 
			
		||||
  // something's wrong
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,52 +1,74 @@
 | 
			
		||||
import cookie from "cookie";
 | 
			
		||||
import * as jwt from "jsonwebtoken";
 | 
			
		||||
import { Action } from "routing-controllers";
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from './config';
 | 
			
		||||
import { IllegalJWTError, NoPermissionError, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError';
 | 
			
		||||
import { IllegalJWTError, NoPermissionError, UserDisabledError, UserNonexistantOrRefreshtokenInvalidError } from './errors/AuthError';
 | 
			
		||||
import { JwtCreator, JwtUser } from './jwtcreator';
 | 
			
		||||
import { User } from './models/entities/User';
 | 
			
		||||
// -----------
 | 
			
		||||
const authchecker = async (action: Action, permissions: string | string[]) => {
 | 
			
		||||
    let required_permissions = undefined
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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 permissions The permissions that the endpoint using @Authorized requires.
 | 
			
		||||
 */
 | 
			
		||||
const authchecker = async (action: Action, permissions: string[] | string) => {
 | 
			
		||||
    let required_permissions = undefined;
 | 
			
		||||
    if (typeof permissions === "string") {
 | 
			
		||||
        required_permissions = [permissions]
 | 
			
		||||
    } else {
 | 
			
		||||
        required_permissions = permissions
 | 
			
		||||
    }
 | 
			
		||||
    // const token = action.request.headers["authorization"];
 | 
			
		||||
    const provided_token = action.request.query["auth"];
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        console.log(error);
 | 
			
		||||
        throw new IllegalJWTError()
 | 
			
		||||
        jwtPayload = await refresh(action);
 | 
			
		||||
    }
 | 
			
		||||
    const count = await getConnectionManager().get().getRepository(User).count({ id: jwtPayload["userdetails"]["id"], refreshTokenCount: jwtPayload["userdetails"]["refreshTokenCount"] })
 | 
			
		||||
    if (count !== 1) {
 | 
			
		||||
        throw new UserNonexistantOrRefreshtokenInvalidError()
 | 
			
		||||
 | 
			
		||||
    const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions'] })
 | 
			
		||||
    if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
 | 
			
		||||
    if (user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
    if (!jwtPayload["permissions"]) { throw new NoPermissionError(); }
 | 
			
		||||
 | 
			
		||||
    action.response.local = {}
 | 
			
		||||
    action.response.local.jwtPayload = jwtPayload;
 | 
			
		||||
    for (let required_permission of required_permissions) {
 | 
			
		||||
        if (!(jwtPayload["permissions"].includes(required_permission))) { return false; }
 | 
			
		||||
    }
 | 
			
		||||
    if (jwtPayload.permissions) {
 | 
			
		||||
        action.response.local = {}
 | 
			
		||||
        action.response.local.jwtPayload = jwtPayload.permissions
 | 
			
		||||
        required_permissions.forEach(r => {
 | 
			
		||||
            const permission_key = r.split(":")[0]
 | 
			
		||||
            const actual_accesslevel_for_permission = jwtPayload.permissions[permission_key]
 | 
			
		||||
            const permission_access_level = r.split(":")[1]
 | 
			
		||||
            if (actual_accesslevel_for_permission.includes(permission_access_level)) {
 | 
			
		||||
                return true;
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new NoPermissionError()
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    } else {
 | 
			
		||||
        throw new NoPermissionError()
 | 
			
		||||
    }
 | 
			
		||||
    // 
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handels 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 {
 | 
			
		||||
        jwt.verify(provided_token, config.jwt_secret);
 | 
			
		||||
        return true
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        return false
 | 
			
		||||
        refresh_token = cookie.parse(action.request.headers["cookie"])["lfk_backend__refresh_token"];
 | 
			
		||||
    }
 | 
			
		||||
    catch {
 | 
			
		||||
        throw new IllegalJWTError();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let jwtPayload = undefined;
 | 
			
		||||
    try {
 | 
			
		||||
        jwtPayload = <any>jwt.verify(refresh_token, config.jwt_secret);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        throw new IllegalJWTError();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const user = await getConnectionManager().get().getRepository(User).findOne({ id: jwtPayload["id"], refreshTokenCount: jwtPayload["refreshTokenCount"] }, { relations: ['permissions', 'groups', 'groups.permissions'] })
 | 
			
		||||
    if (!user) { throw new UserNonexistantOrRefreshtokenInvalidError() }
 | 
			
		||||
    if (user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
 | 
			
		||||
    let newAccess = JwtCreator.createAccess(user);
 | 
			
		||||
    action.response.header("authorization", "Bearer " + newAccess);
 | 
			
		||||
 | 
			
		||||
    return await new JwtUser(user);
 | 
			
		||||
}
 | 
			
		||||
export default authchecker
 | 
			
		||||
@@ -1,22 +1,34 @@
 | 
			
		||||
import { config as configDotenv } from 'dotenv';
 | 
			
		||||
configDotenv();
 | 
			
		||||
export const config = {
 | 
			
		||||
    internal_port: parseInt(process.env.APP_PORT) || 4010,
 | 
			
		||||
    development: process.env.NODE_ENV === "production",
 | 
			
		||||
    jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
 | 
			
		||||
    phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ"
 | 
			
		||||
}
 | 
			
		||||
let errors = 0
 | 
			
		||||
if (typeof config.internal_port !== "number") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.phone_validation_countrycode !== "string") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (config.phone_validation_countrycode.length !== 2) {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.development !== "boolean") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
import { config as configDotenv } from 'dotenv';
 | 
			
		||||
import ValidatorJS from 'validator';
 | 
			
		||||
 | 
			
		||||
configDotenv();
 | 
			
		||||
export const config = {
 | 
			
		||||
    internal_port: parseInt(process.env.APP_PORT) || 4010,
 | 
			
		||||
    development: process.env.NODE_ENV === "production",
 | 
			
		||||
    jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
 | 
			
		||||
    phone_validation_countrycode: process.env.PHONE_COUNTRYCODE || "ZZ",
 | 
			
		||||
    postalcode_validation_countrycode: getPostalCodeLocale()
 | 
			
		||||
}
 | 
			
		||||
let errors = 0
 | 
			
		||||
if (typeof config.internal_port !== "number") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.phone_validation_countrycode !== "string") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (config.phone_validation_countrycode.length !== 2) {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
if (typeof config.development !== "boolean") {
 | 
			
		||||
    errors++
 | 
			
		||||
}
 | 
			
		||||
function getPostalCodeLocale(): any {
 | 
			
		||||
    try {
 | 
			
		||||
        const stringArray: String[] = ValidatorJS.isPostalCodeLocales;
 | 
			
		||||
        let index = stringArray.indexOf(process.env.POSTALCODE_COUNTRYCODE);
 | 
			
		||||
        return ValidatorJS.isPostalCodeLocales[index];
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
export let e = errors
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
import { Body, JsonController, Post } from 'routing-controllers';
 | 
			
		||||
import { Body, CookieParam, JsonController, Param, Post, Req, Res } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { IllegalJWTError, InvalidCredentialsError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UsernameOrEmailNeededError } from '../errors/AuthError';
 | 
			
		||||
import { UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { CreateAuth } from '../models/actions/CreateAuth';
 | 
			
		||||
import { CreateResetToken } from '../models/actions/CreateResetToken';
 | 
			
		||||
import { HandleLogout } from '../models/actions/HandleLogout';
 | 
			
		||||
import { RefreshAuth } from '../models/actions/RefreshAuth';
 | 
			
		||||
import { ResetPassword } from '../models/actions/ResetPassword';
 | 
			
		||||
import { Auth } from '../models/responses/ResponseAuth';
 | 
			
		||||
import { Logout } from '../models/responses/ResponseLogout';
 | 
			
		||||
 | 
			
		||||
@@ -20,15 +22,17 @@ export class AuthController {
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new access token object' })
 | 
			
		||||
	async login(@Body({ validate: true }) createAuth: CreateAuth) {
 | 
			
		||||
	@OpenAPI({ description: 'Login with your username/email and password. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)' })
 | 
			
		||||
	async login(@Body({ validate: true }) createAuth: CreateAuth, @Res() response: any) {
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			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_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
			return response.send(auth)
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return auth
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/logout")
 | 
			
		||||
@@ -38,15 +42,21 @@ export class AuthController {
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@ResponseSchema(PasswordNeededError)
 | 
			
		||||
	@ResponseSchema(InvalidCredentialsError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new access token object' })
 | 
			
		||||
	async logout(@Body({ validate: true }) handleLogout: HandleLogout) {
 | 
			
		||||
	@OpenAPI({ description: 'Logout using your refresh token. <br> This instantly invalidates all your access and refresh tokens.', security: [{ "RefreshTokenCookie": [] }] })
 | 
			
		||||
	async logout(@Body({ validate: true }) handleLogout: HandleLogout, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any) {
 | 
			
		||||
		if (refresh_token && refresh_token.length != 0 && handleLogout.token == undefined) {
 | 
			
		||||
			handleLogout.token = refresh_token;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		let logout;
 | 
			
		||||
		try {
 | 
			
		||||
			logout = await handleLogout.logout()
 | 
			
		||||
			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 });
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			return error;
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return logout
 | 
			
		||||
		return response.send(logout)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/refresh")
 | 
			
		||||
@@ -55,14 +65,40 @@ export class AuthController {
 | 
			
		||||
	@ResponseSchema(IllegalJWTError)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(RefreshTokenCountInvalidError)
 | 
			
		||||
	@OpenAPI({ description: 'refresh a access token' })
 | 
			
		||||
	async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth) {
 | 
			
		||||
	@OpenAPI({ description: 'Refresh your access and refresh tokens using a valid refresh token. <br> You will receive: \n * access token (use it as a bearer token) \n * refresh token (will also be sent as a cookie)', security: [{ "RefreshTokenCookie": [] }] })
 | 
			
		||||
	async refresh(@Body({ validate: true }) refreshAuth: RefreshAuth, @CookieParam("lfk_backend__refresh_token") refresh_token: string, @Res() response: any, @Req() req: any) {
 | 
			
		||||
		if (refresh_token && refresh_token.length != 0 && refreshAuth.token == undefined) {
 | 
			
		||||
			refreshAuth.token = refresh_token;
 | 
			
		||||
		}
 | 
			
		||||
		console.log(req.headers)
 | 
			
		||||
		let auth;
 | 
			
		||||
		try {
 | 
			
		||||
			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_expires_at', auth.refresh_token_expires_at, { expires: new Date(auth.refresh_token_expires_at * 1000), httpOnly: true });
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			return error;
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
		return auth
 | 
			
		||||
		return response.send(auth)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/reset")
 | 
			
		||||
	@ResponseSchema(Auth)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@OpenAPI({ description: "Request a password reset token. <br> This will provide you with a reset token that you can use by posting to /api/auth/reset/{token}." })
 | 
			
		||||
	async getResetToken(@Body({ validate: true }) passwordReset: CreateResetToken) {
 | 
			
		||||
		//This really shouldn't just get returned, but sent via mail or sth like that. But for dev only this is fine.
 | 
			
		||||
		return { "resetToken": await passwordReset.toResetToken() };
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post("/reset/:token")
 | 
			
		||||
	@ResponseSchema(Auth)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError)
 | 
			
		||||
	@ResponseSchema(UsernameOrEmailNeededError)
 | 
			
		||||
	@OpenAPI({ description: "Reset a user's utilising a valid password reset token. <br> This will set the user's password to the one you provided in the body. <br> To get a reset token post to /api/auth/reset with your username." })
 | 
			
		||||
	async resetPassword(@Param("token") token: string, @Body({ validate: true }) passwordReset: ResetPassword) {
 | 
			
		||||
		passwordReset.resetToken = token;
 | 
			
		||||
		return await passwordReset.resetPassword();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										102
									
								
								src/controllers/ImportController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/controllers/ImportController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
import csv from 'csvtojson';
 | 
			
		||||
import { Authorized, Body, ContentType, Controller, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { RunnerGroupNeededError } from '../errors/RunnerErrors';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
 | 
			
		||||
import RawBodyMiddleware from '../middlewares/RawBody';
 | 
			
		||||
import { ImportRunner } from '../models/actions/ImportRunner';
 | 
			
		||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
 | 
			
		||||
@Controller()
 | 
			
		||||
@Authorized(["RUNNER:IMPORT", "TEAM:IMPORT"])
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class ImportController {
 | 
			
		||||
    private runnerController: RunnerController;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the repository of this controller's model/entity.
 | 
			
		||||
     */
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.runnerController = new RunnerController();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/runners/import')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
 | 
			
		||||
    @OpenAPI({ description: "Create new runners from json and insert them into the provided group. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
 | 
			
		||||
    async postJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @QueryParam("group") groupID: number) {
 | 
			
		||||
        if (!groupID) { throw new RunnerGroupNeededError(); }
 | 
			
		||||
        let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
 | 
			
		||||
        for await (let runner of importRunners) {
 | 
			
		||||
            responseRunners.push(await this.runnerController.post(await runner.toCreateRunner(groupID)));
 | 
			
		||||
        }
 | 
			
		||||
        return responseRunners;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/organisations/:id/import')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
 | 
			
		||||
    @OpenAPI({ description: "Create new runners from json and insert them into the provided org. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
 | 
			
		||||
    async postOrgsJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @Param('id') id: number) {
 | 
			
		||||
        return await this.postJSON(importRunners, id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/teams/:id/import')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
 | 
			
		||||
    @OpenAPI({ description: "Create new runners from json and insert them into the provided team" })
 | 
			
		||||
    async postTeamsJSON(@Body({ validate: true, type: ImportRunner }) importRunners: ImportRunner[], @Param('id') id: number) {
 | 
			
		||||
        return await this.postJSON(importRunners, id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/runners/import/csv')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @UseBefore(RawBodyMiddleware)
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
 | 
			
		||||
    @OpenAPI({ description: "Create new runners from csv and insert them into the provided group. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
 | 
			
		||||
    async postCSV(@Req() request: any, @QueryParam("group") groupID: number) {
 | 
			
		||||
        let csvParse = await csv({ delimiter: [",", ";"], trim: true }).fromString(request.rawBody.toString());
 | 
			
		||||
        let importRunners: ImportRunner[] = new Array<ImportRunner>();
 | 
			
		||||
        for await (let runner of csvParse) {
 | 
			
		||||
            let newImportRunner = new ImportRunner();
 | 
			
		||||
            newImportRunner.firstname = runner.firstname;
 | 
			
		||||
            newImportRunner.middlename = runner.middlename;
 | 
			
		||||
            newImportRunner.lastname = runner.lastname;
 | 
			
		||||
            if (runner.class === undefined) { newImportRunner.team = runner.team; }
 | 
			
		||||
            else { newImportRunner.class = runner.class; }
 | 
			
		||||
            importRunners.push(newImportRunner);
 | 
			
		||||
        }
 | 
			
		||||
        return await this.postJSON(importRunners, groupID);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/organisations/:id/import/csv')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @UseBefore(RawBodyMiddleware)
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
 | 
			
		||||
    @OpenAPI({ description: "Create new runners from csv and insert them into the provided org. <br> If teams/classes are provided alongside the runner's name they'll automaticly be created under the provided org and the runners will be inserted into the teams instead." })
 | 
			
		||||
    async postOrgsCSV(@Req() request: any, @Param("id") id: number) {
 | 
			
		||||
        return await this.postCSV(request, id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Post('/teams/:id/import/csv')
 | 
			
		||||
    @ContentType("application/json")
 | 
			
		||||
    @UseBefore(RawBodyMiddleware)
 | 
			
		||||
    @ResponseSchema(ResponseRunner, { isArray: true, statusCode: 200 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(RunnerGroupNeededError, { statusCode: 406 })
 | 
			
		||||
    @OpenAPI({ description: "Create new runners from csv and insert them into the provided team" })
 | 
			
		||||
    async postTeamsCSV(@Req() request: any, @Param("id") id: number) {
 | 
			
		||||
        return await this.postCSV(request, id);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										118
									
								
								src/controllers/PermissionController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/controllers/PermissionController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { PermissionIdsNotMatchingError, PermissionNeedsPrincipalError, PermissionNotFoundError } from '../errors/PermissionErrors';
 | 
			
		||||
import { PrincipalNotFoundError } from '../errors/PrincipalErrors';
 | 
			
		||||
import { CreatePermission } from '../models/actions/CreatePermission';
 | 
			
		||||
import { UpdatePermission } from '../models/actions/UpdatePermission';
 | 
			
		||||
import { Permission } from '../models/entities/Permission';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponsePermission } from '../models/responses/ResponsePermission';
 | 
			
		||||
import { ResponsePrincipal } from '../models/responses/ResponsePrincipal';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/permissions')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class PermissionController {
 | 
			
		||||
    private permissionRepository: Repository<Permission>;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the repository of this controller's model/entity.
 | 
			
		||||
     */
 | 
			
		||||
    constructor() {
 | 
			
		||||
        this.permissionRepository = getConnectionManager().get().getRepository(Permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Get()
 | 
			
		||||
    @Authorized("PERMISSION:GET")
 | 
			
		||||
    @ResponseSchema(ResponsePermission, { isArray: true })
 | 
			
		||||
    @OpenAPI({ description: 'Lists all permissions for all users and groups.' })
 | 
			
		||||
    async getAll() {
 | 
			
		||||
        let responsePermissions: ResponsePermission[] = new Array<ResponsePermission>();
 | 
			
		||||
        const permissions = await this.permissionRepository.find({ relations: ['principal'] });
 | 
			
		||||
        permissions.forEach(permission => {
 | 
			
		||||
            responsePermissions.push(new ResponsePermission(permission));
 | 
			
		||||
        });
 | 
			
		||||
        return responsePermissions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Get('/:id')
 | 
			
		||||
    @Authorized("PERMISSION:GET")
 | 
			
		||||
    @ResponseSchema(ResponsePermission)
 | 
			
		||||
    @ResponseSchema(PermissionNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @OnUndefined(PermissionNotFoundError)
 | 
			
		||||
    @OpenAPI({ description: 'Lists all information about the permission whose id got provided.' })
 | 
			
		||||
    async getOne(@Param('id') id: number) {
 | 
			
		||||
        let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] });
 | 
			
		||||
        if (!permission) { throw new PermissionNotFoundError(); }
 | 
			
		||||
        return new ResponsePermission(permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Post()
 | 
			
		||||
    @Authorized("PERMISSION:CREATE")
 | 
			
		||||
    @ResponseSchema(ResponsePermission)
 | 
			
		||||
    @ResponseSchema(PrincipalNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @OpenAPI({ description: 'Create a new permission for a existing principal(user/group). <br> If a permission with this target, action and prinicpal already exists that permission will be returned instead of creating a new one.' })
 | 
			
		||||
    async post(@Body({ validate: true }) createPermission: CreatePermission) {
 | 
			
		||||
        let permission;
 | 
			
		||||
        try {
 | 
			
		||||
            permission = await createPermission.toPermission();
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            throw error;
 | 
			
		||||
        }
 | 
			
		||||
        let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] });
 | 
			
		||||
        if (existingPermission) { return new ResponsePermission(existingPermission); }
 | 
			
		||||
 | 
			
		||||
        permission = await this.permissionRepository.save(permission);
 | 
			
		||||
        permission = await this.permissionRepository.findOne(permission, { relations: ['principal'] });
 | 
			
		||||
 | 
			
		||||
        return new ResponsePermission(permission);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Put('/:id')
 | 
			
		||||
    @Authorized("PERMISSION:UPDATE")
 | 
			
		||||
    @ResponseSchema(ResponsePrincipal)
 | 
			
		||||
    @ResponseSchema(PermissionNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(PrincipalNotFoundError, { statusCode: 404 })
 | 
			
		||||
    @ResponseSchema(PermissionIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
    @ResponseSchema(PermissionNeedsPrincipalError, { statusCode: 406 })
 | 
			
		||||
    @OpenAPI({ description: "Update a permission object. <br> If updateing the permission object would result in duplicate permission (same target, action and principal) this permission will get deleted and the existing permission will be returned. <br> Please remember that ids can't be changed." })
 | 
			
		||||
    async put(@Param('id') id: number, @Body({ validate: true }) permission: UpdatePermission) {
 | 
			
		||||
        let oldPermission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] });
 | 
			
		||||
 | 
			
		||||
        if (!oldPermission) {
 | 
			
		||||
            throw new PermissionNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (oldPermission.id != permission.id) {
 | 
			
		||||
            throw new PermissionIdsNotMatchingError();
 | 
			
		||||
        }
 | 
			
		||||
        let existingPermission = await this.permissionRepository.findOne({ target: permission.target, action: permission.action, principal: permission.principal }, { relations: ['principal'] });
 | 
			
		||||
        if (existingPermission) {
 | 
			
		||||
            await this.remove(permission.id, true);
 | 
			
		||||
            return new ResponsePermission(existingPermission);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await this.permissionRepository.save(await permission.updatePermission(oldPermission));
 | 
			
		||||
 | 
			
		||||
        return new ResponsePermission(await this.permissionRepository.findOne({ id: permission.id }, { relations: ['principal'] }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Delete('/:id')
 | 
			
		||||
    @Authorized("PERMISSION:DELETE")
 | 
			
		||||
    @ResponseSchema(ResponsePermission)
 | 
			
		||||
    @ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
    @OnUndefined(204)
 | 
			
		||||
    @OpenAPI({ description: 'Deletes the permission whose id you provide. <br> If no permission with this id exists it will just return 204(no content).' })
 | 
			
		||||
    async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
        let permission = await this.permissionRepository.findOne({ id: id }, { relations: ['principal'] });
 | 
			
		||||
        if (!permission) { return null; }
 | 
			
		||||
 | 
			
		||||
        const responsePermission = new ResponsePermission(permission);
 | 
			
		||||
        await this.permissionRepository.delete(permission);
 | 
			
		||||
        return responsePermission;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,102 +1,106 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
 | 
			
		||||
import { CreateRunner } from '../models/actions/CreateRunner';
 | 
			
		||||
import { Runner } from '../models/entities/Runner';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
 | 
			
		||||
 | 
			
		||||
@JsonController('/runners')
 | 
			
		||||
//@Authorized('RUNNERS:read')
 | 
			
		||||
export class RunnerController {
 | 
			
		||||
	private runnerRepository: Repository<Runner>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerRepository = getConnectionManager().get().getRepository(Runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(ResponseRunner, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runners.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
 | 
			
		||||
		const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] });
 | 
			
		||||
		console.log(runners);
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseRunners.push(new ResponseRunner(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseRunners;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a runner of a specified id (if it exists)' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })
 | 
			
		||||
		if (!runner) { throw new RunnerNotFoundError(); }
 | 
			
		||||
		return new ResponseRunner(runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerGroupNeededError)
 | 
			
		||||
	@ResponseSchema(RunnerGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runner object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunner: CreateRunner) {
 | 
			
		||||
		let runner;
 | 
			
		||||
		try {
 | 
			
		||||
			runner = await createRunner.toRunner();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runner = await this.runnerRepository.save(runner)
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a runner object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() runner: Runner) {
 | 
			
		||||
		let oldRunner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunner) {
 | 
			
		||||
			throw new RunnerNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunner.id != runner.id) {
 | 
			
		||||
			throw new RunnerIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerRepository.update(oldRunner, runner);
 | 
			
		||||
		return new ResponseRunner(runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runner (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id });
 | 
			
		||||
		if (!runner) { return null; }
 | 
			
		||||
		const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] });
 | 
			
		||||
 | 
			
		||||
		if (!runner) {
 | 
			
		||||
			throw new RunnerNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerRepository.delete(runner);
 | 
			
		||||
		return new ResponseRunner(responseRunner);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { RunnerGroupNeededError, RunnerIdsNotMatchingError, RunnerNotFoundError } from '../errors/RunnerErrors';
 | 
			
		||||
import { RunnerGroupNotFoundError } from '../errors/RunnerGroupErrors';
 | 
			
		||||
import { CreateRunner } from '../models/actions/CreateRunner';
 | 
			
		||||
import { UpdateRunner } from '../models/actions/UpdateRunner';
 | 
			
		||||
import { Runner } from '../models/entities/Runner';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunner } from '../models/responses/ResponseRunner';
 | 
			
		||||
 | 
			
		||||
@JsonController('/runners')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class RunnerController {
 | 
			
		||||
	private runnerRepository: Repository<Runner>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerRepository = getConnectionManager().get().getRepository(Runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("RUNNER:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunner, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runners from all teams/orgs. <br> This includes the runner\'s group and distance ran.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseRunners: ResponseRunner[] = new Array<ResponseRunner>();
 | 
			
		||||
		const runners = await this.runnerRepository.find({ relations: ['scans', 'group'] });
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseRunners.push(new ResponseRunner(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseRunners;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("RUNNER:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the runner whose id got provided.' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] })
 | 
			
		||||
		if (!runner) { throw new RunnerNotFoundError(); }
 | 
			
		||||
		return new ResponseRunner(runner);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("RUNNER:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerGroupNeededError)
 | 
			
		||||
	@ResponseSchema(RunnerGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runner. <br> Please remeber to provide the runner\'s group\'s id.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunner: CreateRunner) {
 | 
			
		||||
		let runner;
 | 
			
		||||
		try {
 | 
			
		||||
			runner = await createRunner.toRunner();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runner = await this.runnerRepository.save(runner)
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("RUNNER:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the runner whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) runner: UpdateRunner) {
 | 
			
		||||
		let oldRunner = await this.runnerRepository.findOne({ id: id }, { relations: ['group'] });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunner) {
 | 
			
		||||
			throw new RunnerNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunner.id != runner.id) {
 | 
			
		||||
			throw new RunnerIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerRepository.save(await runner.updateRunner(oldRunner));
 | 
			
		||||
		return new ResponseRunner(await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'group'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("RUNNER:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseRunner)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@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) {
 | 
			
		||||
		let runner = await this.runnerRepository.findOne({ id: id });
 | 
			
		||||
		if (!runner) { return null; }
 | 
			
		||||
		const responseRunner = await this.runnerRepository.findOne(runner, { relations: ['scans', 'group'] });
 | 
			
		||||
 | 
			
		||||
		if (!runner) {
 | 
			
		||||
			throw new RunnerNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerRepository.delete(runner);
 | 
			
		||||
		return new ResponseRunner(responseRunner);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,124 +1,127 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { RunnerOrganisationHasRunnersError, RunnerOrganisationHasTeamsError, RunnerOrganisationIdsNotMatchingError, RunnerOrganisationNotFoundError } from '../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { CreateRunnerOrganisation } from '../models/actions/CreateRunnerOrganisation';
 | 
			
		||||
import { 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')
 | 
			
		||||
//@Authorized('RUNNERS:read')
 | 
			
		||||
export class RunnerOrganisationController {
 | 
			
		||||
	private runnerOrganisationRepository: Repository<RunnerOrganisation>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerOrganisationRepository = getConnectionManager().get().getRepository(RunnerOrganisation);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runnerOrganisations.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTeams: ResponseRunnerOrganisation[] = new Array<ResponseRunnerOrganisation>();
 | 
			
		||||
		const runners = await this.runnerOrganisationRepository.find({ relations: ['address', 'contact', 'teams'] });
 | 
			
		||||
		console.log(runners);
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseTeams.push(new ResponseRunnerOrganisation(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTeams;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerOrganisationNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a runnerOrganisation of a specified id (if it exists)' })
 | 
			
		||||
	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()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runnerOrganisation object (id will be generated automagicly).' })
 | 
			
		||||
	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')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a runnerOrganisation object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() runnerOrganisation: RunnerOrganisation) {
 | 
			
		||||
		let oldRunnerOrganisation = await this.runnerOrganisationRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunnerOrganisation) {
 | 
			
		||||
			throw new RunnerOrganisationNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunnerOrganisation.id != runnerOrganisation.id) {
 | 
			
		||||
			throw new RunnerOrganisationIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerOrganisationRepository.update(oldRunnerOrganisation, runnerOrganisation);
 | 
			
		||||
 | 
			
		||||
		runnerOrganisation = await this.runnerOrganisationRepository.findOne(runnerOrganisation, { relations: ['address', 'contact', 'teams'] });
 | 
			
		||||
		return new ResponseRunnerOrganisation(runnerOrganisation);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerOrganisation)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationHasTeamsError, { statusCode: 406 })
 | 
			
		||||
	@ResponseSchema(RunnerOrganisationHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runnerOrganisation (if it exists).' })
 | 
			
		||||
	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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
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,113 +1,116 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
 | 
			
		||||
import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
 | 
			
		||||
import { RunnerTeam } from '../models/entities/RunnerTeam';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/teams')
 | 
			
		||||
//@Authorized('RUNNERS:read')
 | 
			
		||||
export class RunnerTeamController {
 | 
			
		||||
	private runnerTeamRepository: Repository<RunnerTeam>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerTeamRepository = getConnectionManager().get().getRepository(RunnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all runnerTeams.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>();
 | 
			
		||||
		const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		console.log(runners);
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseTeams.push(new ResponseRunnerTeam(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTeams;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerTeamNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a runnerTeam of a specified id (if it exists)' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		if (!runnerTeam) { throw new RunnerTeamNotFoundError(); }
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new runnerTeam object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
 | 
			
		||||
		let runnerTeam;
 | 
			
		||||
		try {
 | 
			
		||||
			runnerTeam = await createRunnerTeam.toRunnerTeam();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.save(runnerTeam);
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a runnerTeam object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() runnerTeam: RunnerTeam) {
 | 
			
		||||
		let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunnerTeam) {
 | 
			
		||||
			throw new RunnerTeamNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunnerTeam.id != runnerTeam.id) {
 | 
			
		||||
			throw new RunnerTeamIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerTeamRepository.update(oldRunnerTeam, runnerTeam);
 | 
			
		||||
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runnerTeam (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let team = await this.runnerTeamRepository.findOne({ id: id });
 | 
			
		||||
		if (!team) { return null; }
 | 
			
		||||
		let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] });
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerTeam.runners.length != 0) {
 | 
			
		||||
				throw new RunnerTeamHasRunnersError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const runnerController = new RunnerController()
 | 
			
		||||
		for (let runner of runnerTeam.runners) {
 | 
			
		||||
			await runnerController.remove(runner.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const responseTeam = new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
		await this.runnerTeamRepository.delete(team);
 | 
			
		||||
		return responseTeam;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { RunnerTeamHasRunnersError, RunnerTeamIdsNotMatchingError, RunnerTeamNotFoundError } from '../errors/RunnerTeamErrors';
 | 
			
		||||
import { CreateRunnerTeam } from '../models/actions/CreateRunnerTeam';
 | 
			
		||||
import { UpdateRunnerTeam } from '../models/actions/UpdateRunnerTeam';
 | 
			
		||||
import { RunnerTeam } from '../models/entities/RunnerTeam';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseRunnerTeam } from '../models/responses/ResponseRunnerTeam';
 | 
			
		||||
import { RunnerController } from './RunnerController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/teams')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class RunnerTeamController {
 | 
			
		||||
	private runnerTeamRepository: Repository<RunnerTeam>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.runnerTeamRepository = getConnectionManager().get().getRepository(RunnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("TEAM:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all teams. <br> This includes their parent organisation and contact (if existing/associated).' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTeams: ResponseRunnerTeam[] = new Array<ResponseRunnerTeam>();
 | 
			
		||||
		const runners = await this.runnerTeamRepository.find({ relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		runners.forEach(runner => {
 | 
			
		||||
			responseTeams.push(new ResponseRunnerTeam(runner));
 | 
			
		||||
		});
 | 
			
		||||
		return responseTeams;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("TEAM:GET")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(RunnerTeamNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Lists all information about the team whose id got provided.' })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let runnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
		if (!runnerTeam) { throw new RunnerTeamNotFoundError(); }
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("TEAM:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new organsisation. <br> Please remember to provide it\'s parent group\'s id.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createRunnerTeam: CreateRunnerTeam) {
 | 
			
		||||
		let runnerTeam;
 | 
			
		||||
		try {
 | 
			
		||||
			runnerTeam = await createRunnerTeam.toRunnerTeam();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.save(runnerTeam);
 | 
			
		||||
		runnerTeam = await this.runnerTeamRepository.findOne(runnerTeam, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("TEAM:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(RunnerTeamNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(RunnerTeamIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the team whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) runnerTeam: UpdateRunnerTeam) {
 | 
			
		||||
		let oldRunnerTeam = await this.runnerTeamRepository.findOne({ id: id }, { relations: ['parentGroup', 'contact'] });
 | 
			
		||||
 | 
			
		||||
		if (!oldRunnerTeam) {
 | 
			
		||||
			throw new RunnerTeamNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldRunnerTeam.id != runnerTeam.id) {
 | 
			
		||||
			throw new RunnerTeamIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.runnerTeamRepository.save(await runnerTeam.updateRunnerTeam(oldRunnerTeam));
 | 
			
		||||
 | 
			
		||||
		return new ResponseRunnerTeam(await this.runnerTeamRepository.findOne({ id: runnerTeam.id }, { relations: ['parentGroup', 'contact'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("TEAM:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseRunnerTeam)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@ResponseSchema(RunnerTeamHasRunnersError, { statusCode: 406 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete 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) {
 | 
			
		||||
		let team = await this.runnerTeamRepository.findOne({ id: id });
 | 
			
		||||
		if (!team) { return null; }
 | 
			
		||||
		let runnerTeam = await this.runnerTeamRepository.findOne(team, { relations: ['parentGroup', 'contact', 'runners'] });
 | 
			
		||||
 | 
			
		||||
		if (!force) {
 | 
			
		||||
			if (runnerTeam.runners.length != 0) {
 | 
			
		||||
				throw new RunnerTeamHasRunnersError();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		const runnerController = new RunnerController()
 | 
			
		||||
		for (let runner of runnerTeam.runners) {
 | 
			
		||||
			await runnerController.remove(runner.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const responseTeam = new ResponseRunnerTeam(runnerTeam);
 | 
			
		||||
		await this.runnerTeamRepository.delete(team);
 | 
			
		||||
		return responseTeam;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										22
									
								
								src/controllers/StatusController.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/controllers/StatusController.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
import { Get, JsonController } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnection } from 'typeorm';
 | 
			
		||||
 | 
			
		||||
@JsonController('/status')
 | 
			
		||||
export class StatusController {
 | 
			
		||||
 | 
			
		||||
    @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." })
 | 
			
		||||
    get() {
 | 
			
		||||
        let connection;
 | 
			
		||||
        try {
 | 
			
		||||
            connection = getConnection();
 | 
			
		||||
        } catch {
 | 
			
		||||
            throw new Error("sth is wrong, i can feel it....");
 | 
			
		||||
        }
 | 
			
		||||
        return {
 | 
			
		||||
            "controllers": "✔",
 | 
			
		||||
            "database connection": "✔"
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
 | 
			
		||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
@@ -9,7 +9,7 @@ import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseTrack } from '../models/responses/ResponseTrack';
 | 
			
		||||
 | 
			
		||||
@JsonController('/tracks')
 | 
			
		||||
//@Authorized("TRACKS:read")
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class TrackController {
 | 
			
		||||
	private trackRepository: Repository<Track>;
 | 
			
		||||
 | 
			
		||||
@@ -21,8 +21,9 @@ export class TrackController {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("TRACK:GET")
 | 
			
		||||
	@ResponseSchema(ResponseTrack, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: "Lists all tracks." })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all tracks.' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseTracks: ResponseTrack[] = new Array<ResponseTrack>();
 | 
			
		||||
		const tracks = await this.trackRepository.find();
 | 
			
		||||
@@ -33,10 +34,11 @@ export class TrackController {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("TRACK:GET")
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(TrackNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: "Returns a track of a specified id (if it exists)" })
 | 
			
		||||
	@OpenAPI({ description: "Lists all information about the track whose id got provided." })
 | 
			
		||||
	async getOne(@Param('id') id: number) {
 | 
			
		||||
		let track = await this.trackRepository.findOne({ id: id });
 | 
			
		||||
		if (!track) { throw new TrackNotFoundError(); }
 | 
			
		||||
@@ -44,8 +46,9 @@ export class TrackController {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("TRACK:CREATE")
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@OpenAPI({ description: "Create a new track object (id will be generated automagicly)." })
 | 
			
		||||
	@OpenAPI({ description: "Create a new track. <br> Please remember that the track\'s distance must be greater than 0." })
 | 
			
		||||
	async post(
 | 
			
		||||
		@Body({ validate: true })
 | 
			
		||||
		track: CreateTrack
 | 
			
		||||
@@ -54,10 +57,11 @@ export class TrackController {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("TRACK:UPDATE")
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@ResponseSchema(TrackNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(TrackIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a track object (id can't be changed)." })
 | 
			
		||||
	@OpenAPI({ description: "Update the track whose id you provided. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() track: Track) {
 | 
			
		||||
		let oldTrack = await this.trackRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
@@ -69,15 +73,16 @@ export class TrackController {
 | 
			
		||||
			throw new TrackIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.trackRepository.update(oldTrack, track);
 | 
			
		||||
		await this.trackRepository.save(track);
 | 
			
		||||
		return new ResponseTrack(track);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("TRACK:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseTrack)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: "Delete a specified track (if it exists)." })
 | 
			
		||||
	@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) {
 | 
			
		||||
		let track = await this.trackRepository.findOne({ id: id });
 | 
			
		||||
		if (!track) { return null; }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,88 +1,108 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
 | 
			
		||||
import { CreateUser } from '../models/actions/CreateUser';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/users')
 | 
			
		||||
export class UserController {
 | 
			
		||||
	private userRepository: Repository<User>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.userRepository = getConnectionManager().get().getRepository(User);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(User, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all users.' })
 | 
			
		||||
	getAll() {
 | 
			
		||||
		return this.userRepository.find();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a user of a specified id (if it exists)' })
 | 
			
		||||
	getOne(@Param('id') id: number) {
 | 
			
		||||
		return this.userRepository.findOne({ id: id });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new user object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createUser: CreateUser) {
 | 
			
		||||
		let user;
 | 
			
		||||
		try {
 | 
			
		||||
			user = await createUser.toUser();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.userRepository.save(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a user object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() user: User) {
 | 
			
		||||
		let oldUser = await this.userRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldUser) {
 | 
			
		||||
			throw new UserNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldUser.id != user.id) {
 | 
			
		||||
			throw new UserIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userRepository.update(oldUser, user);
 | 
			
		||||
		return user;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified runner (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: id });
 | 
			
		||||
		if (!user) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userRepository.delete(user);
 | 
			
		||||
		return user;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { UserIdsNotMatchingError, UserNotFoundError } from '../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../errors/UserGroupErrors';
 | 
			
		||||
import { CreateUser } from '../models/actions/CreateUser';
 | 
			
		||||
import { UpdateUser } from '../models/actions/UpdateUser';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseUser } from '../models/responses/ResponseUser';
 | 
			
		||||
import { PermissionController } from './PermissionController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/users')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class UserController {
 | 
			
		||||
	private userRepository: Repository<User>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.userRepository = getConnectionManager().get().getRepository(User);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("USER:GET")
 | 
			
		||||
	@ResponseSchema(User, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all users. <br> This includes their groups and permissions directly granted to them (if existing/associated).' })
 | 
			
		||||
	async getAll() {
 | 
			
		||||
		let responseUsers: ResponseUser[] = new Array<ResponseUser>();
 | 
			
		||||
		const users = await this.userRepository.find({ relations: ['permissions', 'groups'] });
 | 
			
		||||
		users.forEach(user => {
 | 
			
		||||
			responseUsers.push(new ResponseUser(user));
 | 
			
		||||
		});
 | 
			
		||||
		return responseUsers;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("USER:GET")
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserNotFoundError)
 | 
			
		||||
	@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) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] })
 | 
			
		||||
		if (!user) { throw new UserNotFoundError(); }
 | 
			
		||||
		return new ResponseUser(user);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("USER:CREATE")
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new user. <br> If you want to grant permissions to the user you have to create them seperately by posting to /api/permissions after creating the user.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createUser: CreateUser) {
 | 
			
		||||
		let user;
 | 
			
		||||
		try {
 | 
			
		||||
			user = await createUser.toUser();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		user = await this.userRepository.save(user)
 | 
			
		||||
		return new ResponseUser(await this.userRepository.findOne({ id: user.id }, { relations: ['permissions', 'groups'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("USER:UPDATE")
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(UserNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update the user whose id you provided. <br> To change the permissions directly granted to the user please use /api/permissions instead. <br> Please remember that ids can't be changed." })
 | 
			
		||||
	async put(@Param('id') id: number, @Body({ validate: true }) updateUser: UpdateUser) {
 | 
			
		||||
		let oldUser = await this.userRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldUser) {
 | 
			
		||||
			throw new UserNotFoundError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldUser.id != updateUser.id) {
 | 
			
		||||
			throw new UserIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
		await this.userRepository.save(await updateUser.updateUser(oldUser));
 | 
			
		||||
 | 
			
		||||
		return new ResponseUser(await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("USER:DELETE")
 | 
			
		||||
	@ResponseSchema(User)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete the user whose id you provided. <br> If there are any permissions directly granted to the user they will get deleted as well. <br> If no user with this id exists it will just return 204(no content).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let user = await this.userRepository.findOne({ id: id });
 | 
			
		||||
		if (!user) { return null; }
 | 
			
		||||
		const responseUser = await this.userRepository.findOne({ id: id }, { relations: ['permissions', 'groups'] });;
 | 
			
		||||
 | 
			
		||||
		const permissionControler = new PermissionController();
 | 
			
		||||
		for (let permission of responseUser.permissions) {
 | 
			
		||||
			await permissionControler.remove(permission.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userRepository.delete(user);
 | 
			
		||||
		return new ResponseUser(responseUser);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,87 +1,99 @@
 | 
			
		||||
import { Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
 | 
			
		||||
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
 | 
			
		||||
import { UserGroup } from '../models/entities/UserGroup';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/usergroups')
 | 
			
		||||
export class UserGroupController {
 | 
			
		||||
	private userGroupsRepository: Repository<UserGroup>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.userGroupsRepository = getConnectionManager().get().getRepository(UserGroup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@ResponseSchema(UserGroup, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all usergroups.' })
 | 
			
		||||
	getAll() {
 | 
			
		||||
		return this.userGroupsRepository.find();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@OnUndefined(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Returns a usergroup of a specified id (if it exists)' })
 | 
			
		||||
	getOne(@Param('id') id: number) {
 | 
			
		||||
		return this.userGroupsRepository.findOne({ id: id });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new usergroup object (id will be generated automagicly).' })
 | 
			
		||||
	async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
 | 
			
		||||
		let userGroup;
 | 
			
		||||
		try {
 | 
			
		||||
			userGroup = await createUserGroup.toUserGroup();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.userGroupsRepository.save(userGroup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@ResponseSchema(UserGroupIdsNotMatchingError, { statusCode: 406 })
 | 
			
		||||
	@OpenAPI({ description: "Update a usergroup object (id can't be changed)." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) {
 | 
			
		||||
		let oldUserGroup = await this.userGroupsRepository.findOne({ id: id });
 | 
			
		||||
 | 
			
		||||
		if (!oldUserGroup) {
 | 
			
		||||
			throw new UserGroupNotFoundError()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldUserGroup.id != userGroup.id) {
 | 
			
		||||
			throw new UserGroupIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userGroupsRepository.update(oldUserGroup, userGroup);
 | 
			
		||||
		return userGroup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 204 })
 | 
			
		||||
	@OnUndefined(204)
 | 
			
		||||
	@OpenAPI({ description: 'Delete a specified usergroup (if it exists).' })
 | 
			
		||||
	async remove(@Param("id") id: number) {
 | 
			
		||||
		let group = await this.userGroupsRepository.findOne({ id: id });
 | 
			
		||||
		if (!group) {
 | 
			
		||||
			return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userGroupsRepository.delete(group);
 | 
			
		||||
		return group;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
import { Authorized, Body, Delete, Get, JsonController, OnUndefined, Param, Post, Put, QueryParam } from 'routing-controllers';
 | 
			
		||||
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
 | 
			
		||||
import { getConnectionManager, Repository } from 'typeorm';
 | 
			
		||||
import { EntityFromBody } from 'typeorm-routing-controllers-extensions';
 | 
			
		||||
import { UserGroupIdsNotMatchingError, UserGroupNotFoundError } from '../errors/UserGroupErrors';
 | 
			
		||||
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
 | 
			
		||||
import { UserGroup } from '../models/entities/UserGroup';
 | 
			
		||||
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
 | 
			
		||||
import { ResponseUserGroup } from '../models/responses/ResponseUserGroup';
 | 
			
		||||
import { PermissionController } from './PermissionController';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@JsonController('/usergroups')
 | 
			
		||||
@OpenAPI({ security: [{ "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
 | 
			
		||||
export class UserGroupController {
 | 
			
		||||
	private userGroupsRepository: Repository<UserGroup>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Gets the repository of this controller's model/entity.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor() {
 | 
			
		||||
		this.userGroupsRepository = getConnectionManager().get().getRepository(UserGroup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get()
 | 
			
		||||
	@Authorized("USERGROUP:GET")
 | 
			
		||||
	@ResponseSchema(UserGroup, { isArray: true })
 | 
			
		||||
	@OpenAPI({ description: 'Lists all groups. <br> The information provided might change while the project continues to evolve.' })
 | 
			
		||||
	getAll() {
 | 
			
		||||
		return this.userGroupsRepository.find({ relations: ["permissions"] });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Get('/:id')
 | 
			
		||||
	@Authorized("USERGROUP:GET")
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@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.' })
 | 
			
		||||
	getOne(@Param('id') id: number) {
 | 
			
		||||
		return this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Post()
 | 
			
		||||
	@Authorized("USERGROUP:CREATE")
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError)
 | 
			
		||||
	@OpenAPI({ description: 'Create a new group. <br> If you want to grant permissions to the group you have to create them seperately by posting to /api/permissions after creating the group.' })
 | 
			
		||||
	async post(@Body({ validate: true }) createUserGroup: CreateUserGroup) {
 | 
			
		||||
		let userGroup;
 | 
			
		||||
		try {
 | 
			
		||||
			userGroup = await createUserGroup.toUserGroup();
 | 
			
		||||
		} catch (error) {
 | 
			
		||||
			throw error;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this.userGroupsRepository.save(userGroup);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Put('/:id')
 | 
			
		||||
	@Authorized("USERGROUP:UPDATE")
 | 
			
		||||
	@ResponseSchema(UserGroup)
 | 
			
		||||
	@ResponseSchema(UserGroupNotFoundError, { statusCode: 404 })
 | 
			
		||||
	@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." })
 | 
			
		||||
	async put(@Param('id') id: number, @EntityFromBody() userGroup: UserGroup) {
 | 
			
		||||
		let oldUserGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
 | 
			
		||||
 | 
			
		||||
		if (!oldUserGroup) {
 | 
			
		||||
			throw new UserGroupNotFoundError()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (oldUserGroup.id != userGroup.id) {
 | 
			
		||||
			throw new UserGroupIdsNotMatchingError();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userGroupsRepository.save(userGroup);
 | 
			
		||||
		return userGroup;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Delete('/:id')
 | 
			
		||||
	@Authorized("USERGROUP:DELETE")
 | 
			
		||||
	@ResponseSchema(ResponseUserGroup)
 | 
			
		||||
	@ResponseSchema(ResponseEmpty, { statusCode: 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).' })
 | 
			
		||||
	async remove(@Param("id") id: number, @QueryParam("force") force: boolean) {
 | 
			
		||||
		let group = await this.userGroupsRepository.findOne({ id: id }, { relations: ["permissions"] });
 | 
			
		||||
		if (!group) { return null; }
 | 
			
		||||
		const responseGroup = await this.userGroupsRepository.findOne({ id: id }, { relations: ['permissions'] });
 | 
			
		||||
 | 
			
		||||
		const permissionControler = new PermissionController();
 | 
			
		||||
		for (let permission of responseGroup.permissions) {
 | 
			
		||||
			await permissionControler.remove(permission.id, true);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		await this.userGroupsRepository.delete(group);
 | 
			
		||||
		return new ResponseUserGroup(responseGroup);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,63 +1,57 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { ForbiddenError, NotAcceptableError, NotFoundError, UnauthorizedError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a jwt is expired.
 | 
			
		||||
 */
 | 
			
		||||
export class ExpiredJWTError extends UnauthorizedError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "ExpiredJWTError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "your provided jwt is expired"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a jwt could not be parsed.
 | 
			
		||||
 * For example: Wrong signature or expired.
 | 
			
		||||
 */
 | 
			
		||||
export class IllegalJWTError extends UnauthorizedError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "IllegalJWTError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "your provided jwt could not be parsed"
 | 
			
		||||
	message = "Your provided jwt could not be parsed."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when user is nonexistant or refreshtoken is invalid.
 | 
			
		||||
 * This can happen if someone provides a JWT with a invalid user id or the refreshTokenCount of the user is higher that the provided jwt's is.
 | 
			
		||||
 */
 | 
			
		||||
export class UserNonexistantOrRefreshtokenInvalidError extends UnauthorizedError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserNonexistantOrRefreshtokenInvalidError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "user is nonexistant or refreshtoken is invalid"
 | 
			
		||||
	message = "User is nonexistant or refreshtoken is invalid."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when provided credentials are invalid.
 | 
			
		||||
 * We don't have seperate errors for username/mail and passwords to protect against guessing attacks.
 | 
			
		||||
 */
 | 
			
		||||
export class InvalidCredentialsError extends UnauthorizedError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "InvalidCredentialsError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "your provided credentials are invalid"
 | 
			
		||||
	message = "Your provided credentials are invalid."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a jwt does not have permission for this route/action.
 | 
			
		||||
 * Mainly used be the @Authorized decorator (via the authchecker).
 | 
			
		||||
 */
 | 
			
		||||
export class NoPermissionError extends ForbiddenError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "NoPermissionError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "your provided jwt does not have permission for this route/ action"
 | 
			
		||||
	message = "Your provided jwt does not have permission for this route/ action."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no username and no email is set.
 | 
			
		||||
 * Because we have to identify users somehow.
 | 
			
		||||
 */
 | 
			
		||||
export class UsernameOrEmailNeededError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
@@ -68,47 +62,48 @@ export class UsernameOrEmailNeededError extends NotAcceptableError {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no password is provided.
 | 
			
		||||
 * Error to throw when no password is provided for a new user.
 | 
			
		||||
 * Passwords are the minimum we need for user security.
 | 
			
		||||
 */
 | 
			
		||||
export class PasswordNeededError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "PasswordNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no password is provided - you need to provide it"
 | 
			
		||||
	message = "No password is provided - you need to provide it."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no user could be found mating the provided credential.
 | 
			
		||||
 * Error to throw when no user could be found for a certain query.
 | 
			
		||||
 */
 | 
			
		||||
export class UserNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no user could be found for provided credential"
 | 
			
		||||
	message = "The user you provided couldn't be located in the system. \n Please check your request."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no jwt token was provided (but one had to be).
 | 
			
		||||
 * Error to throw when no jwt was provided (but one had to be).
 | 
			
		||||
 */
 | 
			
		||||
export class JwtNotProvidedError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "JwtNotProvidedError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no jwt token was provided"
 | 
			
		||||
	message = "No jwt was provided."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when user was not found or refresh token count was invalid.
 | 
			
		||||
 * Error to throw when user was not found or the jwt's refresh token count was invalid.
 | 
			
		||||
 */
 | 
			
		||||
export class UserNotFoundOrRefreshTokenCountInvalidError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserNotFoundOrRefreshTokenCountInvalidError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "user was not found or refresh token count was invalid"
 | 
			
		||||
	message = "User was not found or the refresh token count is invalid."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -119,5 +114,27 @@ export class RefreshTokenCountInvalidError extends NotAcceptableError {
 | 
			
		||||
	name = "RefreshTokenCountInvalidError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "refresh token count was invalid"
 | 
			
		||||
	message = "Refresh token count is invalid."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when someone tryes to reset a user's password more than once in 15 minutes.
 | 
			
		||||
 */
 | 
			
		||||
export class ResetAlreadyRequestedError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "ResetAlreadyRequestedError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You already requested a password reset in the last 15 minutes. \n Please wait until the old reset code expires before requesting a new one."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when someone tries a disabled user's password or login as a disabled user.
 | 
			
		||||
 */
 | 
			
		||||
export class UserDisabledError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UserDisabledError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This user is currently disabled. \n Please contact your administrator if this is a mistake."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/errors/PermissionErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/errors/PermissionErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a permission couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class PermissionNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "PermissionNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Permission not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two permissions' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a permission's id.
 | 
			
		||||
 */
 | 
			
		||||
export class PermissionIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "PermissionIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a permission's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a permission gets provided without a principal.
 | 
			
		||||
 */
 | 
			
		||||
export class PermissionNeedsPrincipalError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "PermissionNeedsPrincipalError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "You provided no principal for this permission."
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/errors/PrincipalErrors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/errors/PrincipalErrors.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a user couldn't be found.
 | 
			
		||||
 */
 | 
			
		||||
export class PrincipalNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "PrincipalNotFoundError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "Principal not found!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw, when a provided runnerOrganisation doesn't belong to the accepted types.
 | 
			
		||||
 */
 | 
			
		||||
export class PrincipalWrongTypeError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "PrincipalWrongTypeError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The princial must have an existing principal's id. \n You provided a object of another type."
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a runner couldn't be found.
 | 
			
		||||
 * Implemented this ways to work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
@@ -16,14 +15,13 @@ export class RunnerNotFoundError extends NotFoundError {
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two runners' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a runner's id.
 | 
			
		||||
 * Implemented this ways to work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ import { NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a runner group couldn't be found.
 | 
			
		||||
 * Implemented this ways to work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerGroupNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a runner organisation couldn't be found.
 | 
			
		||||
 * Implemented this ways to work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
@@ -15,39 +14,36 @@ export class RunnerOrganisationNotFoundError extends NotFoundError {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two runner organisations' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a runner's id.
 | 
			
		||||
 * Implemented this way to work with the json-schema conversion for openapi.
 | 
			
		||||
 * Usually occurs when a user tries to change a runner organisation's id.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a runner organisation's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a organisation still has runners associated.
 | 
			
		||||
 * Implemented this waysto work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationHasRunnersError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationHasRunnersError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query."
 | 
			
		||||
	message = "This organisation still has runners associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a organisation still has runners associated.
 | 
			
		||||
 * Implemented this waysto work with the json-schema conversion for openapi.
 | 
			
		||||
 * Error to throw when a organisation still has teams associated.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerOrganisationHasTeamsError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerOrganisationHasTeamsError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams ass `?force` to your query."
 | 
			
		||||
	message = "This organisation still has teams associated with it. \n If you want to delete this organisation with all it's runners and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,6 @@ import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a runner team couldn't be found.
 | 
			
		||||
 * Implemented this ways to work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerTeamNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
@@ -15,32 +14,29 @@ export class RunnerTeamNotFoundError extends NotFoundError {
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two runner teams' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a runner's id.
 | 
			
		||||
 * Implemented this way to work with the json-schema conversion for openapi.
 | 
			
		||||
 * Usually occurs when a user tries to change a runner team's id.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerTeamIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerTeamIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The id's don't match!! \n And if you wanted to change a runner's id: This isn't allowed"
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a runner's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a team still has runners associated.
 | 
			
		||||
 * Implemented this waysto work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerTeamHasRunnersError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "RunnerTeamHasRunnersError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams ass `?force` to your query."
 | 
			
		||||
	message = "This team still has runners associated with it. \n If you want to delete this team with all it's runners and teams add `?force` to your query."
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a team still has runners associated.
 | 
			
		||||
 * Implemented this waysto work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class RunnerTeamNeedsParentError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,8 @@
 | 
			
		||||
import { JsonController, Param, Body, Get, Post, Put, Delete, NotFoundError, OnUndefined, NotAcceptableError } from 'routing-controllers';
 | 
			
		||||
import { IsInt, IsNotEmpty, IsPositive, IsString } from 'class-validator';
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when a track couldn't be found.
 | 
			
		||||
 * Implemented this ways to work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class TrackNotFoundError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
@@ -16,12 +15,11 @@ export class TrackNotFoundError extends NotFoundError {
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when two tracks' ids don't match.
 | 
			
		||||
 * Usually occurs when a user tries to change a track's id.
 | 
			
		||||
 * Implemented this ways to work with the json-schema conversion for openapi.
 | 
			
		||||
 */
 | 
			
		||||
export class TrackIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "TrackIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The id's don't match!! \n And if you wanted to change a track's id: This isn't allowed"
 | 
			
		||||
	message = "The ids don't match! \n And if you wanted to change a track's id: This isn't allowed"
 | 
			
		||||
}
 | 
			
		||||
@@ -3,14 +3,15 @@ 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 :)
 | 
			
		||||
 */
 | 
			
		||||
export class UsernameOrEmailNeededError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "UsernameOrEmailNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no username or email is set!"
 | 
			
		||||
	message = "No username or email is set!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -33,5 +34,5 @@ export class UserIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	name = "UserIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The id's don't match!! \n And if you wanted to change a user's id: This isn't allowed"
 | 
			
		||||
	message = "The ids don't match!! \n And if you wanted to change a user's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
@@ -2,14 +2,14 @@ import { IsString } from 'class-validator';
 | 
			
		||||
import { NotAcceptableError, NotFoundError } from 'routing-controllers';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Error to throw when no groupname is set
 | 
			
		||||
 * Error to throw when no groupname is set.
 | 
			
		||||
 */
 | 
			
		||||
export class GroupNameNeededError extends NotFoundError {
 | 
			
		||||
	@IsString()
 | 
			
		||||
	name = "GroupNameNeededError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "no groupname is set!"
 | 
			
		||||
	message = "No name is set for this group!"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -32,5 +32,5 @@ export class UserGroupIdsNotMatchingError extends NotAcceptableError {
 | 
			
		||||
	name = "UserGroupIdsNotMatchingError"
 | 
			
		||||
 | 
			
		||||
	@IsString()
 | 
			
		||||
	message = "The id's don't match!! \n If you wanted to change a usergroup's id: This isn't allowed"
 | 
			
		||||
	message = "The ids don't match!! \n If you wanted to change a usergroup's id: This isn't allowed!"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								src/jwtcreator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/jwtcreator.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from "jsonwebtoken";
 | 
			
		||||
import { config } from './config';
 | 
			
		||||
import { User } from './models/entities/User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is responsible for all things JWT creation.
 | 
			
		||||
 */
 | 
			
		||||
export class JwtCreator {
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new refresh token for a given user
 | 
			
		||||
     * @param user User entity that the refresh token shall be created for
 | 
			
		||||
     * @param expiry_timestamp Timestamp for the token expiry. Will be generated if not provided.
 | 
			
		||||
     */
 | 
			
		||||
    public static createRefresh(user: User, expiry_timestamp?: number) {
 | 
			
		||||
        if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 10 * 36000; }
 | 
			
		||||
        return jsonwebtoken.sign({
 | 
			
		||||
            refreshTokenCount: user.refreshTokenCount,
 | 
			
		||||
            id: user.id,
 | 
			
		||||
            exp: expiry_timestamp
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new access token for a given user
 | 
			
		||||
     * @param user User entity that the access token shall be created for
 | 
			
		||||
     * @param expiry_timestamp Timestamp for the token expiry. Will be generated if not provided.
 | 
			
		||||
     */
 | 
			
		||||
    public static createAccess(user: User, expiry_timestamp?: number) {
 | 
			
		||||
        if (!expiry_timestamp) { expiry_timestamp = Math.floor(Date.now() / 1000) + 10 * 36000; }
 | 
			
		||||
        return jsonwebtoken.sign({
 | 
			
		||||
            userdetails: new JwtUser(user),
 | 
			
		||||
            exp: expiry_timestamp
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new password reset token for a given user.
 | 
			
		||||
     * The token is valid for 15 minutes or 1 use - whatever comes first.
 | 
			
		||||
     * @param user User entity that the password reset token shall be created for
 | 
			
		||||
     */
 | 
			
		||||
    public static createReset(user: User) {
 | 
			
		||||
        let expiry_timestamp = Math.floor(Date.now() / 1000) + 15 * 60;
 | 
			
		||||
        return jsonwebtoken.sign({
 | 
			
		||||
            id: user.id,
 | 
			
		||||
            refreshTokenCount: user.refreshTokenCount,
 | 
			
		||||
            exp: expiry_timestamp
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Special variant of the user class that 
 | 
			
		||||
 */
 | 
			
		||||
export class JwtUser {
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    @IsUUID(4)
 | 
			
		||||
    uuid: string;
 | 
			
		||||
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    permissions: string[];
 | 
			
		||||
 | 
			
		||||
    @IsBoolean()
 | 
			
		||||
    enabled: boolean;
 | 
			
		||||
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    refreshTokenCount?: number;
 | 
			
		||||
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    profilePic?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new instance of this class based on a provided user entity.
 | 
			
		||||
     * @param user User entity that shall be encapsulated in a jwt.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(user: User) {
 | 
			
		||||
        this.id = user.id;
 | 
			
		||||
        this.firstname = user.firstname;
 | 
			
		||||
        this.middlename = user.middlename;
 | 
			
		||||
        this.lastname = user.lastname;
 | 
			
		||||
        this.username = user.username;
 | 
			
		||||
        this.email = user.email;
 | 
			
		||||
        this.refreshTokenCount = user.refreshTokenCount;
 | 
			
		||||
        this.uuid = user.uuid;
 | 
			
		||||
        this.profilePic = user.profilePic;
 | 
			
		||||
        this.permissions = 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,10 +1,16 @@
 | 
			
		||||
import { createConnection } from "typeorm";
 | 
			
		||||
 | 
			
		||||
import { runSeeder } from 'typeorm-seeding';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import SeedUsers from '../seeds/SeedUsers';
 | 
			
		||||
/**
 | 
			
		||||
 * Loader for the database that creates the database connection and initializes the database tabels.
 | 
			
		||||
 * It also triggers the seeding process if no users got detected in the database.
 | 
			
		||||
 */
 | 
			
		||||
export default async () => {
 | 
			
		||||
    const connection = await createConnection();
 | 
			
		||||
    connection.synchronize();
 | 
			
		||||
    await connection.synchronize();
 | 
			
		||||
    if (await connection.getRepository(User).count() === 0) {
 | 
			
		||||
        await runSeeder(SeedUsers);
 | 
			
		||||
    }
 | 
			
		||||
    return connection;
 | 
			
		||||
};
 | 
			
		||||
@@ -1,10 +1,13 @@
 | 
			
		||||
import cookieParser from "cookie-parser";
 | 
			
		||||
import { Application } from "express";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loader for express related configurations.
 | 
			
		||||
 * Currently only enables the proxy trust.
 | 
			
		||||
 * Configures proxy trusts, globally used middlewares and other express features.
 | 
			
		||||
 */
 | 
			
		||||
export default async (app: Application) => {
 | 
			
		||||
	app.enable('trust proxy');
 | 
			
		||||
	app.disable('x-powered-by');
 | 
			
		||||
	app.disable('x-served-by');
 | 
			
		||||
	app.use(cookieParser());
 | 
			
		||||
	return app;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import openapiLoader from "./openapi";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Index Loader that executes the other loaders in the right order.
 | 
			
		||||
 * This basicly exists for abstraction and a overall better dev experience.
 | 
			
		||||
 */
 | 
			
		||||
export default async (app: Application) => {
 | 
			
		||||
    await databaseLoader();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,12 @@
 | 
			
		||||
import { validationMetadatasToSchemas } from "class-validator-jsonschema";
 | 
			
		||||
import { Application } from "express";
 | 
			
		||||
import express, { Application } from "express";
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import { getMetadataArgsStorage } from "routing-controllers";
 | 
			
		||||
import { routingControllersToSpec } from "routing-controllers-openapi";
 | 
			
		||||
import * as swaggerUiExpress from "swagger-ui-express";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Loader for everything openapi related - from creating the schema to serving it via a static route.
 | 
			
		||||
 * Loader for everything openapi related - from creating the schema to serving it via a static route and swaggerUiExpress.
 | 
			
		||||
 * All auth schema related stuff also has to be configured here
 | 
			
		||||
 */
 | 
			
		||||
export default async (app: Application) => {
 | 
			
		||||
  const storage = getMetadataArgsStorage();
 | 
			
		||||
@@ -26,29 +27,27 @@ export default async (app: Application) => {
 | 
			
		||||
          "AuthToken": {
 | 
			
		||||
            "type": "http",
 | 
			
		||||
            "scheme": "bearer",
 | 
			
		||||
            "bearerFormat": "JWT"
 | 
			
		||||
            "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."
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      info: {
 | 
			
		||||
        description: "The the backend API for the LfK! runner system.",
 | 
			
		||||
        title: "LfK! Backend API",
 | 
			
		||||
        version: "1.0.0",
 | 
			
		||||
        version: "0.0.5",
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  //Options for swaggerUiExpress
 | 
			
		||||
  const options = {
 | 
			
		||||
    explorer: true,
 | 
			
		||||
  };
 | 
			
		||||
  app.use(
 | 
			
		||||
    "/api/docs",
 | 
			
		||||
    swaggerUiExpress.serve,
 | 
			
		||||
    swaggerUiExpress.setup(spec, options)
 | 
			
		||||
  );
 | 
			
		||||
  app.get(["/api/openapi.json", "/api/swagger.json"], (req, res) => {
 | 
			
		||||
  app.get(["/api/docs/openapi.json", "/api/docs/swagger.json"], (req, res) => {
 | 
			
		||||
    res.json(spec);
 | 
			
		||||
  });
 | 
			
		||||
  app.use('/api/docs', express.static(path.join(__dirname, '../static/docs'), { index: "index.html", extensions: ['html'] }));
 | 
			
		||||
  return app;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { ExpressErrorMiddlewareInterface, Middleware } from "routing-controllers";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Our Error handling middlware that returns our custom httperrors to the user
 | 
			
		||||
 * Our Error handling middlware that returns our custom httperrors to the user.
 | 
			
		||||
 */
 | 
			
		||||
@Middleware({ type: "after" })
 | 
			
		||||
export class ErrorHandler implements ExpressErrorMiddlewareInterface {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								src/middlewares/RawBody.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/middlewares/RawBody.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import { Request, Response } from 'express';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Custom express middleware that appends the raw body to the request obeject.
 | 
			
		||||
 * Mainly used for parsing csvs from boddies.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const RawBodyMiddleware = (req: Request, res: Response, next: () => void) => {
 | 
			
		||||
    const body = []
 | 
			
		||||
    req.on('data', chunk => {
 | 
			
		||||
        body.push(chunk)
 | 
			
		||||
    })
 | 
			
		||||
    req.on('end', () => {
 | 
			
		||||
        const rawBody = Buffer.concat(body)
 | 
			
		||||
        req['rawBody'] = rawBody
 | 
			
		||||
        next()
 | 
			
		||||
    })
 | 
			
		||||
    req.on('error', () => {
 | 
			
		||||
        res.sendStatus(400)
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RawBodyMiddleware
 | 
			
		||||
@@ -1,16 +1,20 @@
 | 
			
		||||
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 address's description.
 | 
			
		||||
   */
 | 
			
		||||
     * The newaddress's description.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    description?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's first line.
 | 
			
		||||
     * The new address's first line.
 | 
			
		||||
     * Containing the street and house number.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
@@ -18,7 +22,7 @@ export class CreateAddress {
 | 
			
		||||
    address1: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's second line.
 | 
			
		||||
     * The new address's second line.
 | 
			
		||||
     * Containing optional information.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
@@ -26,29 +30,31 @@ export class CreateAddress {
 | 
			
		||||
    address2?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's postal code.
 | 
			
		||||
     * The new address's postal code.
 | 
			
		||||
     * This will get checked against the postal code syntax for the configured country.
 | 
			
		||||
     * TODO: Implement the config option. 
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsPostalCode("DE")
 | 
			
		||||
    @IsPostalCode(config.postalcode_validation_countrycode)
 | 
			
		||||
    postalcode: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's city.
 | 
			
		||||
     * The new address's city.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    city: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The address's country.
 | 
			
		||||
     * The new address's country.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    country: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Address object based on this.
 | 
			
		||||
     * Creates a new Address entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public toAddress(): Address {
 | 
			
		||||
        let newAddress: Address = new Address();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,47 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsEmail, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from 'jsonwebtoken';
 | 
			
		||||
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { InvalidCredentialsError, PasswordNeededError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
 | 
			
		||||
import { JwtCreator } from '../../jwtcreator';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { Auth } from '../responses/ResponseAuth';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create auth credentials based on user credentials provided in a json body (post request).
 | 
			
		||||
 * To be a little bit more exact: Is takes in a username/email + password and creates a new access and refresh token for the user.
 | 
			
		||||
 * It of course checks for user existance, password validity and so on.
 | 
			
		||||
 */
 | 
			
		||||
export class CreateAuth {
 | 
			
		||||
    /**
 | 
			
		||||
     * The username of the user that want's to login.
 | 
			
		||||
     * Either username or email have to be provided.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
    @IsString()
 | 
			
		||||
    password: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The email address of the user that want's to login.
 | 
			
		||||
     * Either username or email have to be provided.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's password.
 | 
			
		||||
     * Will be checked against an argon2 hash.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    password: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new auth object based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toAuth(): Promise<Auth> {
 | 
			
		||||
        let newAuth: Auth = new Auth();
 | 
			
		||||
 | 
			
		||||
@@ -26,34 +49,25 @@ export class CreateAuth {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.password) {
 | 
			
		||||
            throw new PasswordNeededError()
 | 
			
		||||
            throw new PasswordNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        const found_users = await getConnectionManager().get().getRepository(User).find({ relations: ['groups', 'permissions'], where: [{ username: this.username }, { email: this.email }] });
 | 
			
		||||
        if (found_users.length === 0) {
 | 
			
		||||
            throw new UserNotFoundError()
 | 
			
		||||
        } else {
 | 
			
		||||
            const found_user = found_users[0]
 | 
			
		||||
            if (await argon2.verify(found_user.password, this.password + found_user.uuid)) {
 | 
			
		||||
                const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
 | 
			
		||||
                found_user.permissions = found_user.permissions || []
 | 
			
		||||
                delete found_user.password;
 | 
			
		||||
                newAuth.access_token = jsonwebtoken.sign({
 | 
			
		||||
                    userdetails: found_user,
 | 
			
		||||
                    exp: timestamp_accesstoken_expiry
 | 
			
		||||
                }, config.jwt_secret)
 | 
			
		||||
                newAuth.access_token_expires_at = timestamp_accesstoken_expiry
 | 
			
		||||
                // 
 | 
			
		||||
                const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
 | 
			
		||||
                newAuth.refresh_token = jsonwebtoken.sign({
 | 
			
		||||
                    refreshtokencount: found_user.refreshTokenCount,
 | 
			
		||||
                    userid: found_user.id,
 | 
			
		||||
                    exp: timestamp_refresh_expiry
 | 
			
		||||
                }, config.jwt_secret)
 | 
			
		||||
                newAuth.refresh_token_expires_at = timestamp_refresh_expiry
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new InvalidCredentialsError()
 | 
			
		||||
            }
 | 
			
		||||
        const found_user = await getConnectionManager().get().getRepository(User).findOne({ relations: ['groups', 'permissions', 'groups.permissions'], where: [{ username: this.username }, { email: this.email }] });
 | 
			
		||||
        if (!found_user) {
 | 
			
		||||
            throw new UserNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
        if (found_user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
        if (!(await argon2.verify(found_user.password, this.password + found_user.uuid))) {
 | 
			
		||||
            throw new InvalidCredentialsError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Create the access token
 | 
			
		||||
        const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
 | 
			
		||||
        newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry);
 | 
			
		||||
        newAuth.access_token_expires_at = timestamp_accesstoken_expiry
 | 
			
		||||
        //Create the refresh token
 | 
			
		||||
        const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
 | 
			
		||||
        newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry);
 | 
			
		||||
        newAuth.refresh_token_expires_at = timestamp_refresh_expiry
 | 
			
		||||
        return newAuth;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,84 +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';
 | 
			
		||||
 | 
			
		||||
export class CreateGroupContact {
 | 
			
		||||
    /**
 | 
			
		||||
       * The contact's first name.
 | 
			
		||||
       */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's middle name.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's address.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's phone number.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The contact's email address.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's this participant's address from this.address.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Address object based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toGroupContact(): Promise<GroupContact> {
 | 
			
		||||
        let contact: GroupContact = new GroupContact();
 | 
			
		||||
        contact.firstname = this.firstname;
 | 
			
		||||
        contact.middlename = this.middlename;
 | 
			
		||||
        contact.lastname = this.lastname;
 | 
			
		||||
        contact.email = this.email;
 | 
			
		||||
        contact.phone = this.phone;
 | 
			
		||||
        contact.address = await this.getAddress();
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,72 +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';
 | 
			
		||||
 | 
			
		||||
export abstract class CreateParticipant {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's middle name.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's phone number.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's e-mail address.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new participant's address.
 | 
			
		||||
     * Must be of type number (address id), createAddress (new address) or address (existing address)
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's this participant's address from this.address.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(Address).findOne({ id: this.address });
 | 
			
		||||
            if (!address) { throw new AddressNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new AddressWrongTypeError;
 | 
			
		||||
    }
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								src/models/actions/CreatePermission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/models/actions/CreatePermission.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsEnum,
 | 
			
		||||
    IsInt,
 | 
			
		||||
    IsNotEmpty
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { PrincipalNotFoundError } from '../../errors/PrincipalErrors';
 | 
			
		||||
import { Permission } from '../entities/Permission';
 | 
			
		||||
import { Principal } from '../entities/Principal';
 | 
			
		||||
import { PermissionAction } from '../enums/PermissionAction';
 | 
			
		||||
import { PermissionTarget } from '../enums/PermissionTargets';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new Permission entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreatePermission {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new permissions's principal's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    principal: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new permissions's target.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsEnum(PermissionTarget)
 | 
			
		||||
    target: PermissionTarget;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new permissions's action.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsEnum(PermissionAction)
 | 
			
		||||
    action: PermissionAction;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new Permission entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toPermission(): Promise<Permission> {
 | 
			
		||||
        let newPermission: Permission = new Permission();
 | 
			
		||||
 | 
			
		||||
        newPermission.principal = await this.getPrincipal();
 | 
			
		||||
        newPermission.target = this.target;
 | 
			
		||||
        newPermission.action = this.action;
 | 
			
		||||
 | 
			
		||||
        return newPermission;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the new permission's principal by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getPrincipal(): Promise<Principal> {
 | 
			
		||||
        let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal })
 | 
			
		||||
        if (!principal) { throw new PrincipalNotFoundError(); }
 | 
			
		||||
        return principal;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								src/models/actions/CreateResetToken.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/models/actions/CreateResetToken.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
import { IsEmail, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { ResetAlreadyRequestedError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
 | 
			
		||||
import { JwtCreator } from '../../jwtcreator';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This calss is used to create password reset tokens for users.
 | 
			
		||||
 * These password reset token can be used to set a new password for the user for the next 15mins.
 | 
			
		||||
 */
 | 
			
		||||
export class CreateResetToken {
 | 
			
		||||
    /**
 | 
			
		||||
     * The username of the user that wants to reset their password.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The email address of the user that wants to reset their password.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a password reset token based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toResetToken(): Promise<any> {
 | 
			
		||||
        if (this.email === undefined && this.username === undefined) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        let found_user = await getConnectionManager().get().getRepository(User).findOne({ where: [{ username: this.username }, { email: this.email }] });
 | 
			
		||||
        if (!found_user) { throw new UserNotFoundError(); }
 | 
			
		||||
        if (found_user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
        if (found_user.resetRequestedTimestamp > (Math.floor(Date.now() / 1000) - 15 * 60)) { throw new ResetAlreadyRequestedError(); }
 | 
			
		||||
 | 
			
		||||
        found_user.refreshTokenCount = found_user.refreshTokenCount + 1;
 | 
			
		||||
        found_user.resetRequestedTimestamp = Math.floor(Date.now() / 1000);
 | 
			
		||||
        await getConnectionManager().get().getRepository(User).save(found_user);
 | 
			
		||||
 | 
			
		||||
        //Create the reset token
 | 
			
		||||
        let reset_token = JwtCreator.createReset(found_user);
 | 
			
		||||
 | 
			
		||||
        return reset_token;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,17 +7,19 @@ import { Runner } from '../entities/Runner';
 | 
			
		||||
import { RunnerGroup } from '../entities/RunnerGroup';
 | 
			
		||||
import { CreateParticipant } from './CreateParticipant';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new Runner entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateRunner extends CreateParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runner's team's id.
 | 
			
		||||
     * Either provide this or his organisation's id.
 | 
			
		||||
     * The new runner's group's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    group: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Runner entity from this.
 | 
			
		||||
     * Creates a new Runner entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toRunner(): Promise<Runner> {
 | 
			
		||||
        let newRunner: Runner = new Runner();
 | 
			
		||||
@@ -34,10 +36,10 @@ export class CreateRunner extends CreateParticipant {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Manages all the different ways a group can be provided.
 | 
			
		||||
     * Gets the new runner's group by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroup(): Promise<RunnerGroup> {
 | 
			
		||||
        if (this.group === undefined) {
 | 
			
		||||
        if (this.group === undefined || this.group === null) {
 | 
			
		||||
            throw new RunnerTeamNeedsParentError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.group)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,19 @@ 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 group's name.
 | 
			
		||||
     * The new group's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The group's contact.
 | 
			
		||||
     * The new group's contact.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
@@ -20,16 +23,16 @@ export abstract class CreateRunnerGroup {
 | 
			
		||||
    contact?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Deals with the contact for groups this.
 | 
			
		||||
     * Gets the new group's contact by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getContact(): Promise<GroupContact> {
 | 
			
		||||
        if (this.contact === undefined) {
 | 
			
		||||
        if (this.contact === undefined || this.contact === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.contact)) {
 | 
			
		||||
            let address = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
 | 
			
		||||
            if (!address) { throw new GroupContactNotFoundError; }
 | 
			
		||||
            return address;
 | 
			
		||||
            let contact = await getConnectionManager().get().getRepository(GroupContact).findOne({ id: this.contact });
 | 
			
		||||
            if (!contact) { throw new GroupContactNotFoundError; }
 | 
			
		||||
            return contact;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new GroupContactWrongTypeError;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,21 +5,23 @@ 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), createAddress (new address) or address (existing address)
 | 
			
		||||
     * Optional.
 | 
			
		||||
     * Must be of type number (address id).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    address?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a Participant entity from this.
 | 
			
		||||
     * Gets the org's address by it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getAddress(): Promise<Address> {
 | 
			
		||||
        if (this.address === undefined) {
 | 
			
		||||
        if (this.address === undefined || this.address === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.address)) {
 | 
			
		||||
@@ -32,7 +34,7 @@ export class CreateRunnerOrganisation extends CreateRunnerGroup {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a RunnerOrganisation entity from this.
 | 
			
		||||
     * Creates a new RunnerOrganisation entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toRunnerOrganisation(): Promise<RunnerOrganisation> {
 | 
			
		||||
        let newRunnerOrganisation: RunnerOrganisation = new RunnerOrganisation();
 | 
			
		||||
 
 | 
			
		||||
@@ -6,17 +6,23 @@ import { RunnerOrganisation } from '../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerTeam } from '../entities/RunnerTeam';
 | 
			
		||||
import { CreateRunnerGroup } from './CreateRunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new RunnerTeam entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateRunnerTeam extends CreateRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The team's parent group (organisation).
 | 
			
		||||
     * The new team's parent group (organisation).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    parentGroup: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the new team's parent org based on it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getParent(): Promise<RunnerOrganisation> {
 | 
			
		||||
        if (this.parentGroup === undefined) {
 | 
			
		||||
        if (this.parentGroup === undefined || this.parentGroup === null) {
 | 
			
		||||
            throw new RunnerTeamNeedsParentError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.parentGroup)) {
 | 
			
		||||
@@ -29,7 +35,7 @@ export class CreateRunnerTeam extends CreateRunnerGroup {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a RunnerTeam entity from this.
 | 
			
		||||
     * Creates a new RunnerTeam entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toRunnerTeam(): Promise<RunnerTeam> {
 | 
			
		||||
        let newRunnerTeam: RunnerTeam = new RunnerTeam();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,26 @@
 | 
			
		||||
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 track's name.
 | 
			
		||||
     * The new track's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The track's distance in meters (must be greater 0).
 | 
			
		||||
     * The new track's distance in meters (must be greater than 0).
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsPositive()
 | 
			
		||||
    distance: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts a Track object based on this.
 | 
			
		||||
     * Creates a new Track entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public toTrack(): Track {
 | 
			
		||||
        let newTrack: Track = new Track();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,120 +1,124 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
 | 
			
		||||
export class CreateUser {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's middle name.
 | 
			
		||||
     * Optinal.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's username.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's email address.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's phone number.
 | 
			
		||||
     * Optional
 | 
			
		||||
     */
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's password.
 | 
			
		||||
     * This will of course not be saved in plaintext :)
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    password: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's groups' id(s).
 | 
			
		||||
     * You can provide either one groupId or an array of groupIDs.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groupId?: number[] | number
 | 
			
		||||
 | 
			
		||||
    //TODO: ProfilePics
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts this to a User Entity.
 | 
			
		||||
     */
 | 
			
		||||
    public async toUser(): Promise<User> {
 | 
			
		||||
        let newUser: User = new User();
 | 
			
		||||
 | 
			
		||||
        if (this.email === undefined && this.username === undefined) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this.groupId) {
 | 
			
		||||
            if (!Array.isArray(this.groupId)) {
 | 
			
		||||
                this.groupId = [this.groupId]
 | 
			
		||||
            }
 | 
			
		||||
            const groupIDs: number[] = this.groupId
 | 
			
		||||
            let errors = 0
 | 
			
		||||
            const validateusergroups = async () => {
 | 
			
		||||
                let foundgroups = []
 | 
			
		||||
                for (const g of groupIDs) {
 | 
			
		||||
                    const found = await getConnectionManager().get().getRepository(UserGroup).find({ id: g });
 | 
			
		||||
                    if (found.length === 0) {
 | 
			
		||||
                        errors++
 | 
			
		||||
                    } else {
 | 
			
		||||
                        foundgroups.push(found[0])
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                newUser.groups = foundgroups
 | 
			
		||||
            }
 | 
			
		||||
            await validateusergroups()
 | 
			
		||||
            if (errors !== 0) {
 | 
			
		||||
                throw new UserGroupNotFoundError();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        newUser.email = this.email
 | 
			
		||||
        newUser.username = this.username
 | 
			
		||||
        newUser.firstname = this.firstname
 | 
			
		||||
        newUser.middlename = this.middlename
 | 
			
		||||
        newUser.lastname = this.lastname
 | 
			
		||||
        newUser.uuid = uuid.v4()
 | 
			
		||||
        newUser.phone = this.phone
 | 
			
		||||
        newUser.password = await argon2.hash(this.password + newUser.uuid);
 | 
			
		||||
        //TODO: ProfilePics
 | 
			
		||||
 | 
			
		||||
        return newUser;
 | 
			
		||||
    }
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsBoolean, IsEmail, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import * as uuid from 'uuid';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../errors/UserErrors';
 | 
			
		||||
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new User entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateUser {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's middle name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's username.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's email address.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's phone number.
 | 
			
		||||
     * This will be validated against the configured country phone numer syntax (default: international).
 | 
			
		||||
     */
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's password.
 | 
			
		||||
     * This will of course not be saved in plaintext :)
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    password: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Will the new user be enabled from the start?
 | 
			
		||||
     * Default: true
 | 
			
		||||
     */
 | 
			
		||||
    @IsBoolean()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    enabled?: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new user's groups' id(s).
 | 
			
		||||
     * You can provide either one groupId or an array of groupIDs.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groups?: number[] | number
 | 
			
		||||
 | 
			
		||||
    //TODO: ProfilePics
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts this to a User entity.
 | 
			
		||||
     */
 | 
			
		||||
    public async toUser(): Promise<User> {
 | 
			
		||||
        let newUser: User = new User();
 | 
			
		||||
 | 
			
		||||
        if (this.email === undefined && this.username === undefined) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        newUser.email = this.email
 | 
			
		||||
        newUser.username = this.username
 | 
			
		||||
        newUser.firstname = this.firstname
 | 
			
		||||
        newUser.middlename = this.middlename
 | 
			
		||||
        newUser.lastname = this.lastname
 | 
			
		||||
        newUser.uuid = uuid.v4()
 | 
			
		||||
        newUser.phone = this.phone
 | 
			
		||||
        newUser.password = await argon2.hash(this.password + newUser.uuid);
 | 
			
		||||
        newUser.groups = await this.getGroups();
 | 
			
		||||
        newUser.enabled = this.enabled;
 | 
			
		||||
        //TODO: ProfilePics
 | 
			
		||||
 | 
			
		||||
        return newUser;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's all groups for this user by their id's;
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroups() {
 | 
			
		||||
        if (!this.groups) { return null; }
 | 
			
		||||
        let groups = new Array<UserGroup>();
 | 
			
		||||
        if (!Array.isArray(this.groups)) {
 | 
			
		||||
            this.groups = [this.groups]
 | 
			
		||||
        }
 | 
			
		||||
        for (let group of this.groups) {
 | 
			
		||||
            let found = await getConnectionManager().get().getRepository(UserGroup).findOne({ id: group });
 | 
			
		||||
            if (!found) { throw new UserGroupNotFoundError(); }
 | 
			
		||||
            groups.push(found);
 | 
			
		||||
        }
 | 
			
		||||
        return groups;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
import { IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This classed is used to create a new UserGroup entity from a json body (post request).
 | 
			
		||||
 */
 | 
			
		||||
export class CreateUserGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * The new group's name.
 | 
			
		||||
@@ -17,7 +20,7 @@ export class CreateUserGroup {
 | 
			
		||||
    description?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts this to a UserGroup entity.
 | 
			
		||||
     * Creates a new UserGroup entity from this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toUserGroup(): Promise<UserGroup> {
 | 
			
		||||
        let newUserGroup: UserGroup = new UserGroup();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from 'jsonwebtoken';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
@@ -6,10 +6,23 @@ import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, Us
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { Logout } from '../responses/ResponseLogout';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class handels a user logging out of the system.
 | 
			
		||||
 * Of course it check's the user's provided credential (token) before logging him out.
 | 
			
		||||
 */
 | 
			
		||||
export class HandleLogout {
 | 
			
		||||
    /**
 | 
			
		||||
     * A stringyfied jwt access token.
 | 
			
		||||
     * Will get checked for validity.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    token: string;
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    token?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logs the user out.
 | 
			
		||||
     * This gets achived by increasing the user's refresh token count, thereby invalidateing all currently existing jwts for that user.
 | 
			
		||||
     */
 | 
			
		||||
    public async logout(): Promise<Logout> {
 | 
			
		||||
        let logout: Logout = new Logout();
 | 
			
		||||
        if (!this.token || this.token === undefined) {
 | 
			
		||||
@@ -22,11 +35,11 @@ export class HandleLogout {
 | 
			
		||||
            throw new IllegalJWTError()
 | 
			
		||||
        }
 | 
			
		||||
        logout.timestamp = Math.floor(Date.now() / 1000)
 | 
			
		||||
        let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] });
 | 
			
		||||
        let found_user: User = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] });
 | 
			
		||||
        if (!found_user) {
 | 
			
		||||
            throw new UserNotFoundError()
 | 
			
		||||
        }
 | 
			
		||||
        if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
 | 
			
		||||
        if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) {
 | 
			
		||||
            throw new RefreshTokenCountInvalidError()
 | 
			
		||||
        }
 | 
			
		||||
        found_user.refreshTokenCount++;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								src/models/actions/ImportRunner.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/models/actions/ImportRunner.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { RunnerGroupNeededError } from '../../errors/RunnerErrors';
 | 
			
		||||
import { RunnerOrganisationNotFoundError } from '../../errors/RunnerOrganisationErrors';
 | 
			
		||||
import { RunnerGroup } from '../entities/RunnerGroup';
 | 
			
		||||
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerTeam } from '../entities/RunnerTeam';
 | 
			
		||||
import { CreateRunner } from './CreateRunner';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Special class used to import runners from csv files - or json arrays created from csv to be exact.
 | 
			
		||||
 * Why you ask? Because the past has shown us that a non excel/csv based workflow is too much for most schools.
 | 
			
		||||
 */
 | 
			
		||||
export class ImportRunner {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runner's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runner's middle name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runner's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new runner's team's name (if not provided otherwise).
 | 
			
		||||
     * The team will automaticly get generated if it doesn't exist in this org yet.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    team?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Just an alias for team, because this is usually only used for importing data from schools.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    public set class(value: string) {
 | 
			
		||||
        this.team = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a CreateRunner object based on this.
 | 
			
		||||
     * @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of.
 | 
			
		||||
     */
 | 
			
		||||
    public async toCreateRunner(groupID: number): Promise<CreateRunner> {
 | 
			
		||||
        let newRunner: CreateRunner = new CreateRunner();
 | 
			
		||||
 | 
			
		||||
        newRunner.firstname = this.firstname;
 | 
			
		||||
        newRunner.middlename = this.middlename;
 | 
			
		||||
        newRunner.lastname = this.lastname;
 | 
			
		||||
        newRunner.group = (await this.getGroup(groupID)).id;
 | 
			
		||||
 | 
			
		||||
        return newRunner;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get's the new runners group.
 | 
			
		||||
     * @param groupID Either the id of the new runner's group or the id of the org that the new runner's team is a part of.
 | 
			
		||||
     */
 | 
			
		||||
    public async getGroup(groupID: number): Promise<RunnerGroup> {
 | 
			
		||||
        if (this.team === undefined && groupID === undefined) {
 | 
			
		||||
            throw new RunnerGroupNeededError();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ id: groupID });
 | 
			
		||||
        if (team) { return team; }
 | 
			
		||||
 | 
			
		||||
        let org = await getConnectionManager().get().getRepository(RunnerOrganisation).findOne({ id: groupID });
 | 
			
		||||
        if (!org) {
 | 
			
		||||
            throw new RunnerOrganisationNotFoundError();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.team === undefined) { return org; }
 | 
			
		||||
 | 
			
		||||
        team = await getConnectionManager().get().getRepository(RunnerTeam).findOne({ name: this.team, parentGroup: org });
 | 
			
		||||
        if (!team) {
 | 
			
		||||
            let newRunnerTeam: RunnerTeam = new RunnerTeam();
 | 
			
		||||
            newRunnerTeam.name = this.team;
 | 
			
		||||
            newRunnerTeam.parentGroup = org;
 | 
			
		||||
            team = await getConnectionManager().get().getRepository(RunnerTeam).save(newRunnerTeam);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return team;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,15 +1,29 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
import { IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from 'jsonwebtoken';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { IllegalJWTError, JwtNotProvidedError, RefreshTokenCountInvalidError, UserDisabledError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { JwtCreator } from "../../jwtcreator";
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { Auth } from '../responses/ResponseAuth';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to create refreshed auth credentials.
 | 
			
		||||
 * To be a little bit more exact: Is takes in a refresh token and creates a new access and refresh token for it's user.
 | 
			
		||||
 * It of course checks for user existance, jwt validity and so on.
 | 
			
		||||
 */
 | 
			
		||||
export class RefreshAuth {
 | 
			
		||||
    /**
 | 
			
		||||
     * A stringyfied jwt refresh token.
 | 
			
		||||
     * Will get checked for validity.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    token: string;
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    token?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new auth object based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async toAuth(): Promise<Auth> {
 | 
			
		||||
        let newAuth: Auth = new Auth();
 | 
			
		||||
        if (!this.token || this.token === undefined) {
 | 
			
		||||
@@ -21,31 +35,22 @@ export class RefreshAuth {
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            throw new IllegalJWTError()
 | 
			
		||||
        }
 | 
			
		||||
        const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["userid"] }, { relations: ['groups', 'permissions'] });
 | 
			
		||||
        const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] }, { relations: ['groups', 'permissions', 'groups.permissions'] });
 | 
			
		||||
        if (!found_user) {
 | 
			
		||||
            throw new UserNotFoundError()
 | 
			
		||||
        }
 | 
			
		||||
        if (found_user.refreshTokenCount !== decoded["refreshtokencount"]) {
 | 
			
		||||
        if (found_user.enabled == false) { throw new UserDisabledError(); }
 | 
			
		||||
        if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) {
 | 
			
		||||
            throw new RefreshTokenCountInvalidError()
 | 
			
		||||
        }
 | 
			
		||||
        found_user.permissions = found_user.permissions || []
 | 
			
		||||
        delete found_user.password;
 | 
			
		||||
        //Create the auth token
 | 
			
		||||
        const timestamp_accesstoken_expiry = Math.floor(Date.now() / 1000) + 5 * 60
 | 
			
		||||
        delete found_user.password;
 | 
			
		||||
        newAuth.access_token = jsonwebtoken.sign({
 | 
			
		||||
            userdetails: found_user,
 | 
			
		||||
            exp: timestamp_accesstoken_expiry
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
        newAuth.access_token = JwtCreator.createAccess(found_user, timestamp_accesstoken_expiry);
 | 
			
		||||
        newAuth.access_token_expires_at = timestamp_accesstoken_expiry
 | 
			
		||||
        // 
 | 
			
		||||
        //Create the refresh token
 | 
			
		||||
        const timestamp_refresh_expiry = Math.floor(Date.now() / 1000) + 10 * 36000
 | 
			
		||||
        newAuth.refresh_token = jsonwebtoken.sign({
 | 
			
		||||
            refreshtokencount: found_user.refreshTokenCount,
 | 
			
		||||
            userid: found_user.id,
 | 
			
		||||
            exp: timestamp_refresh_expiry
 | 
			
		||||
        }, config.jwt_secret)
 | 
			
		||||
        newAuth.refresh_token_expires_at = timestamp_refresh_expiry
 | 
			
		||||
 | 
			
		||||
        newAuth.refresh_token = JwtCreator.createRefresh(found_user, timestamp_refresh_expiry);
 | 
			
		||||
        newAuth.refresh_token_expires_at = timestamp_refresh_expiry;
 | 
			
		||||
        return newAuth;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								src/models/actions/ResetPassword.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/models/actions/ResetPassword.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,57 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import * as jsonwebtoken from 'jsonwebtoken';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { IllegalJWTError, JwtNotProvidedError, PasswordNeededError, RefreshTokenCountInvalidError, UserNotFoundError } from '../../errors/AuthError';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class can be used to reset a user's password.
 | 
			
		||||
 * To set a new password the user needs to provide a valid password reset token.
 | 
			
		||||
 */
 | 
			
		||||
export class ResetPassword {
 | 
			
		||||
    /**
 | 
			
		||||
     * The reset token on which the password reset will be based.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    resetToken?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's new password
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    password: string;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a password reset token based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async resetPassword(): Promise<any> {
 | 
			
		||||
        if (!this.resetToken || this.resetToken === undefined) {
 | 
			
		||||
            throw new JwtNotProvidedError()
 | 
			
		||||
        }
 | 
			
		||||
        if (!this.password || this.password === undefined) {
 | 
			
		||||
            throw new PasswordNeededError()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let decoded;
 | 
			
		||||
        try {
 | 
			
		||||
            decoded = jsonwebtoken.verify(this.resetToken, config.jwt_secret)
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            throw new IllegalJWTError()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const found_user = await getConnectionManager().get().getRepository(User).findOne({ id: decoded["id"] });
 | 
			
		||||
        if (!found_user) { throw new UserNotFoundError(); }
 | 
			
		||||
        if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { throw new RefreshTokenCountInvalidError(); }
 | 
			
		||||
 | 
			
		||||
        found_user.refreshTokenCount = found_user.refreshTokenCount + 1;
 | 
			
		||||
        found_user.password = await argon2.hash(this.password + found_user.uuid);
 | 
			
		||||
        await getConnectionManager().get().getRepository(User).save(found_user);
 | 
			
		||||
 | 
			
		||||
        return "password reset successfull";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								src/models/actions/UpdatePermission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/models/actions/UpdatePermission.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
import { IsInt, IsNotEmpty, IsObject } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { PermissionNeedsPrincipalError } from '../../errors/PermissionErrors';
 | 
			
		||||
import { PrincipalNotFoundError, PrincipalWrongTypeError } from '../../errors/PrincipalErrors';
 | 
			
		||||
import { Permission } from '../entities/Permission';
 | 
			
		||||
import { Principal } from '../entities/Principal';
 | 
			
		||||
import { PermissionAction } from '../enums/PermissionAction';
 | 
			
		||||
import { PermissionTarget } from '../enums/PermissionTargets';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a Permission entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export class UpdatePermission {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated permission'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 permissions's principal.
 | 
			
		||||
     * Just has to contain the principal's id -everything else won't be checked or changed.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    principal: Principal;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The permissions's target.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    target: PermissionTarget;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The permissions's action.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    action: PermissionAction;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a provided Permission entity based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async updatePermission(permission: Permission): Promise<Permission> {
 | 
			
		||||
        permission.principal = await this.getPrincipal();
 | 
			
		||||
        permission.target = this.target;
 | 
			
		||||
        permission.action = this.action;
 | 
			
		||||
 | 
			
		||||
        return permission;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the updated permission's principal based on it's id.
 | 
			
		||||
     */
 | 
			
		||||
    public async getPrincipal(): Promise<Principal> {
 | 
			
		||||
        if (this.principal === undefined || this.principal === null) {
 | 
			
		||||
            throw new PermissionNeedsPrincipalError();
 | 
			
		||||
        }
 | 
			
		||||
        if (!isNaN(this.principal.id)) {
 | 
			
		||||
            let principal = await getConnectionManager().get().getRepository(Principal).findOne({ id: this.principal.id });
 | 
			
		||||
            if (!principal) { throw new PrincipalNotFoundError(); }
 | 
			
		||||
            return principal;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new PrincipalWrongTypeError();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										130
									
								
								src/models/actions/UpdateUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/models/actions/UpdateUser.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
import * as argon2 from "argon2";
 | 
			
		||||
import { IsBoolean, IsEmail, IsInt, IsOptional, IsPhoneNumber, IsString } from 'class-validator';
 | 
			
		||||
import { getConnectionManager } from 'typeorm';
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { UsernameOrEmailNeededError } from '../../errors/AuthError';
 | 
			
		||||
import { UserGroupNotFoundError } from '../../errors/UserGroupErrors';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is used to update a User entity (via put request).
 | 
			
		||||
 */
 | 
			
		||||
export class UpdateUser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user'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 user's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's middle name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's username.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's email address.
 | 
			
		||||
     * You have to provide at least one of: {email, username}.
 | 
			
		||||
     */
 | 
			
		||||
    @IsEmail()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's phone number.
 | 
			
		||||
     * This will be validated against the configured country phone numer syntax (default: international).
 | 
			
		||||
     */
 | 
			
		||||
    @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The new updated's password. 
 | 
			
		||||
     * Only provide it if you want it updated.
 | 
			
		||||
     * Changeing the password will invalidate all of the user's jwts.
 | 
			
		||||
     * This will of course not be saved in plaintext :)
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    password?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Should the user be enabled?
 | 
			
		||||
     */
 | 
			
		||||
    @IsBoolean()
 | 
			
		||||
    enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The updated user's groups.
 | 
			
		||||
     * This just has to contain the group's id - everything else won't be changed.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groups?: UserGroup[]
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates a provided User entity based on this.
 | 
			
		||||
     */
 | 
			
		||||
    public async updateUser(user: User): Promise<User> {
 | 
			
		||||
        user.email = this.email;
 | 
			
		||||
        user.username = this.username;
 | 
			
		||||
        if ((user.email === undefined || user.email === null) && (user.username === undefined || user.username === null)) {
 | 
			
		||||
            throw new UsernameOrEmailNeededError();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.password) {
 | 
			
		||||
            user.password = await argon2.hash(this.password + user.uuid);
 | 
			
		||||
            user.refreshTokenCount = user.refreshTokenCount + 1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        user.enabled = this.enabled;
 | 
			
		||||
        user.firstname = this.firstname
 | 
			
		||||
        user.middlename = this.middlename
 | 
			
		||||
        user.lastname = this.lastname
 | 
			
		||||
        user.phone = this.phone;
 | 
			
		||||
        user.groups = await this.getGroups();
 | 
			
		||||
        //TODO: ProfilePics
 | 
			
		||||
 | 
			
		||||
        return user;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the updated user's groups based on their ids.
 | 
			
		||||
     */
 | 
			
		||||
    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.id });
 | 
			
		||||
            if (!found) { throw new UserGroupNotFoundError(); }
 | 
			
		||||
            groups.push(found);
 | 
			
		||||
        }
 | 
			
		||||
        return groups;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,11 +6,13 @@ import {
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Participant } from "./Participant";
 | 
			
		||||
import { RunnerOrganisation } from "./RunnerOrganisation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a address (to be used for contact information).
 | 
			
		||||
 * Defines the Address entity.
 | 
			
		||||
 * Implemented this way to prevent any formatting differences.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class Address {
 | 
			
		||||
@@ -23,6 +25,7 @@ export class Address {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's description.
 | 
			
		||||
   * Optional and mostly for UX.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
@@ -49,11 +52,12 @@ export class Address {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The address's postal code.
 | 
			
		||||
   * This will get checked against the postal code syntax for the configured country.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsPostalCode("DE")
 | 
			
		||||
  @IsPostalCode(config.postalcode_validation_countrycode)
 | 
			
		||||
  postalcode: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
@@ -4,19 +4,21 @@ import { Donation } from "./Donation";
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a distance based donation.
 | 
			
		||||
 * Here people donate a certain amout per kilometer
 | 
			
		||||
 * Defines the DistanceDonation entity.
 | 
			
		||||
 * For distanceDonations a donor pledges to donate a certain amount for each kilometer ran by a runner.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class DistanceDonation extends Donation {
 | 
			
		||||
  /**
 | 
			
		||||
   * The runner associated.
 | 
			
		||||
   * The donation's associated runner.
 | 
			
		||||
   * Used as the source of the donation's distance.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Runner, runner => runner.distanceDonations)
 | 
			
		||||
  runner: Runner;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount donated per distance.
 | 
			
		||||
   * The amount the donor set to be donated per kilometer that the runner ran.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
@@ -26,12 +28,12 @@ export class DistanceDonation extends Donation {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The donation's amount in cents (or whatever your currency's smallest unit is.).
 | 
			
		||||
   * The exact implementation may differ for each type of donation.
 | 
			
		||||
   * Get's calculated from the runner's distance ran and the amount donated per kilometer.
 | 
			
		||||
   */
 | 
			
		||||
  public get amount(): number {
 | 
			
		||||
    let calculatedAmount = -1;
 | 
			
		||||
    try {
 | 
			
		||||
      calculatedAmount = this.amountPerDistance * this.runner.distance;
 | 
			
		||||
      calculatedAmount = this.amountPerDistance * (this.runner.distance / 1000);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,9 @@ import { Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typ
 | 
			
		||||
import { Participant } from "./Participant";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the donation interface.
 | 
			
		||||
 * Defines the Donation entity.
 | 
			
		||||
 * A donation just associates a donor with a donation amount.
 | 
			
		||||
 * The specifics of the amoun's determination has to be implemented in child classes.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,13 @@ import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { Participant } from "./Participant";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a donor.
 | 
			
		||||
 * Defines the Donor entity.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class Donor extends Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * Does this donor need a receipt?.
 | 
			
		||||
   * Default: false
 | 
			
		||||
   * Does this donor need a receipt?
 | 
			
		||||
   * Will later be used to automaticly generate donation receipts.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,8 @@ import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a fixed donation.
 | 
			
		||||
 * Defines the FixedDonation entity.
 | 
			
		||||
 * In the past there was no easy way to track fixed donations (eg. for creating donation receipts).
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class FixedDonation extends Donation {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,83 +1,83 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsEmail,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPhoneNumber,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a group's contact.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class GroupContact {
 | 
			
		||||
  /**
 | 
			
		||||
 * Autogenerated unique id (primary key).
 | 
			
		||||
 */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's first name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's middle name.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's last name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Address, address => address.participants, { nullable: true })
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's phone number.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's email address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
    * Used to link contacts to groups.
 | 
			
		||||
    */
 | 
			
		||||
  @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true })
 | 
			
		||||
  groups: RunnerGroup[];
 | 
			
		||||
import {
 | 
			
		||||
  IsEmail,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPhoneNumber,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the GroupContact entity.
 | 
			
		||||
 * Mainly it's own class to reduce duplicate code and enable contact's to be associated with multiple groups.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class GroupContact {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's first name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's middle name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's last name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's address.
 | 
			
		||||
   * This is a address object to prevent any formatting differences.
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Address, address => address.participants, { nullable: true })
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's phone number.
 | 
			
		||||
   * This will be validated against the configured country phone numer syntax (default: international).
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The contact's email address.
 | 
			
		||||
   * Could later be used to automaticly send mails concerning the contact's associated groups.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
    * Used to link contacts to groups.
 | 
			
		||||
    */
 | 
			
		||||
  @OneToMany(() => RunnerGroup, group => group.contact, { nullable: true })
 | 
			
		||||
  groups: RunnerGroup[];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,83 +1,84 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsEmail,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPhoneNumber,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the participant interface.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's first name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's middle name.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's last name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @ManyToOne(() => Address, address => address.participants, { nullable: true })
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's phone number.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's email address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link the participant as the donor of a donation.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Donation, donation => donation.donor, { nullable: true })
 | 
			
		||||
  donations: Donation[];
 | 
			
		||||
import {
 | 
			
		||||
  IsEmail,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsPhoneNumber,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Address } from "./Address";
 | 
			
		||||
import { Donation } from "./Donation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the Participant entity.
 | 
			
		||||
 * Participans can donate and therefor be associated with donation entities.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's first name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's middle name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's last name.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's address.
 | 
			
		||||
   * This is a address object to prevent any formatting differences.
 | 
			
		||||
   */
 | 
			
		||||
  @ManyToOne(() => Address, address => address.participants, { nullable: true })
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's phone number.
 | 
			
		||||
   * This will be validated against the configured country phone numer syntax (default: international).
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsPhoneNumber(config.phone_validation_countrycode)
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The participant's email address.
 | 
			
		||||
   * Can be used to contact the participant.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link the participant as the donor of a donation.
 | 
			
		||||
   * Attention: Only runner's can be associated as a distanceDonations distance source.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Donation, donation => donation.donor, { nullable: true })
 | 
			
		||||
  donations: Donation[];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +1,19 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsEnum,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
 | 
			
		||||
  IsString
 | 
			
		||||
  IsNotEmpty
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { User } from './User';
 | 
			
		||||
import { UserGroup } from './UserGroup';
 | 
			
		||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { PermissionAction } from '../enums/PermissionAction';
 | 
			
		||||
import { PermissionTarget } from '../enums/PermissionTargets';
 | 
			
		||||
import { Principal } from './Principal';
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the Permission interface.
 | 
			
		||||
 * Defines the Permission entity.
 | 
			
		||||
 * Permissions can be granted to principals.
 | 
			
		||||
 * The permissions possible targets and actions are defined in enums.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export abstract class Permission {
 | 
			
		||||
export class Permission {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
@@ -20,30 +22,33 @@ export abstract class Permission {
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * users
 | 
			
		||||
   * The permission's principal.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => User, user => user.permissions, { nullable: true })
 | 
			
		||||
  users: User[]
 | 
			
		||||
  @ManyToOne(() => Principal, principal => principal.permissions)
 | 
			
		||||
  principal: Principal;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * groups
 | 
			
		||||
   * The permission's target.
 | 
			
		||||
   * This get's stored as the enum value's string representation for compatability reasons.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => UserGroup, group => group.permissions, { nullable: true })
 | 
			
		||||
  groups: UserGroup[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The target
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @Column({ type: 'varchar' })
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  target: string;
 | 
			
		||||
  @IsEnum(PermissionTarget)
 | 
			
		||||
  target: PermissionTarget;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The action type
 | 
			
		||||
   * The permission's action.
 | 
			
		||||
   * This get's stored as the enum value's string representation for compatability reasons.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  action: string;
 | 
			
		||||
  @Column({ type: 'varchar' })
 | 
			
		||||
  @IsEnum(PermissionAction)
 | 
			
		||||
  action: PermissionAction;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turn this into a string for exporting and jwts.
 | 
			
		||||
   * Mainly used to shrink the size of jwts (otherwise the would contain entire objects).
 | 
			
		||||
   */
 | 
			
		||||
  public toString(): string {
 | 
			
		||||
    return this.target + ":" + this.action;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								src/models/entities/Principal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/models/entities/Principal.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
import { IsInt } from 'class-validator';
 | 
			
		||||
import { Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm';
 | 
			
		||||
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
 | 
			
		||||
import { Permission } from './Permission';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the principal entity.
 | 
			
		||||
 * A principal basicly is any entity that can receive permissions for the api (users and their groups).
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
export abstract class Principal {
 | 
			
		||||
  /**
 | 
			
		||||
  * Autogenerated unique id (primary key).
 | 
			
		||||
  */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * The participant's permissions.
 | 
			
		||||
  */
 | 
			
		||||
  @OneToMany(() => Permission, permission => permission.principal, { nullable: true })
 | 
			
		||||
  permissions: Permission[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public abstract toResponse(): ResponsePrincipal;
 | 
			
		||||
}
 | 
			
		||||
@@ -7,44 +7,52 @@ import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { Scan } from "./Scan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a runner.
 | 
			
		||||
 * Defines the runner entity.
 | 
			
		||||
 * Runners differ from participants in being able to actually accumulate a ran distance through scans.
 | 
			
		||||
 * Runner's get organized in groups.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class Runner extends Participant {
 | 
			
		||||
  /**
 | 
			
		||||
   * The runner's associated group.
 | 
			
		||||
   * Can be a runner team or organisation.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerGroup, group => group.runners, { nullable: false })
 | 
			
		||||
  group: RunnerGroup;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link runners to donations.
 | 
			
		||||
   * The runner's associated distanceDonations.
 | 
			
		||||
   * Used to link runners to distanceDonations in order to calculate the donation's amount based on the distance the runner ran.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner, { nullable: true })
 | 
			
		||||
  distanceDonations: DistanceDonation[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link runners to cards.
 | 
			
		||||
   * The runner's associated cards.
 | 
			
		||||
   * Used to link runners to cards - yes a runner be associated with multiple cards this came in handy in the past.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => RunnerCard, card => card.runner, { nullable: true })
 | 
			
		||||
  cards: RunnerCard[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link runners to a scans
 | 
			
		||||
   * The runner's associated scans.
 | 
			
		||||
   * Used to link runners to scans (valid and fraudulant).
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Scan, scan => scan.runner, { nullable: true })
 | 
			
		||||
  scans: Scan[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns all valid scans associated with this runner.
 | 
			
		||||
   * This is implemented here to avoid duplicate code in other files.
 | 
			
		||||
   */
 | 
			
		||||
  public get validScans(): Scan[] {
 | 
			
		||||
    return this.scans.filter(scan => { scan.valid === true });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the total distance ran by this runner.
 | 
			
		||||
   * Returns the total distance ran by this runner based on all his valid scans.
 | 
			
		||||
   * This is implemented here to avoid duplicate code in other files.
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  public get distance(): number {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,9 @@ import { Runner } from "./Runner";
 | 
			
		||||
import { TrackScan } from "./TrackScan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a card that can be scanned via a scanner station.
 | 
			
		||||
 * Defines the RunnerCard entity.
 | 
			
		||||
 * A runnerCard is a physical representation for a runner.
 | 
			
		||||
 * It can be associated with a runner to create scans via the scan station's.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class RunnerCard {
 | 
			
		||||
@@ -23,7 +25,8 @@ export class RunnerCard {
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The runner that is currently associated with this card.
 | 
			
		||||
   * The card's currently associated runner.
 | 
			
		||||
   * To increase reusability a card can be reassigned.
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Runner, runner => runner.cards, { nullable: true })
 | 
			
		||||
@@ -32,7 +35,7 @@ export class RunnerCard {
 | 
			
		||||
  /**
 | 
			
		||||
   * The card's code.
 | 
			
		||||
   * This has to be able to being converted to something barcode compatible.
 | 
			
		||||
   * could theoretically be autogenerated
 | 
			
		||||
   * Will get automaticlly generated (not implemented yet).
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsEAN()
 | 
			
		||||
@@ -49,7 +52,8 @@ export class RunnerCard {
 | 
			
		||||
  enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link cards to a track scans.
 | 
			
		||||
   * The card's associated scans.
 | 
			
		||||
   * Used to link cards to track scans.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
 | 
			
		||||
  scans: TrackScan[];
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,8 @@ import { GroupContact } from "./GroupContact";
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the runnerGroup interface.
 | 
			
		||||
 * Defines the RunnerGroup entity.
 | 
			
		||||
 * This is used to group runners together (as the name suggests).
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
@@ -31,13 +32,14 @@ export abstract class RunnerGroup {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's contact.
 | 
			
		||||
   * Optional
 | 
			
		||||
   * This is mostly a feature for the group managers and public relations.
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => GroupContact, contact => contact.groups, { nullable: true })
 | 
			
		||||
  contact?: GroupContact;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's associated runners.
 | 
			
		||||
   * Used to link runners to a runner group.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => Runner, runner => runner.group, { nullable: true })
 | 
			
		||||
 
 | 
			
		||||
@@ -5,22 +5,23 @@ import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { RunnerTeam } from "./RunnerTeam";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a runner organisation (business or school for example).
 | 
			
		||||
 * Defines the RunnerOrganisation entity.
 | 
			
		||||
 * This usually is a school, club or company.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class RunnerOrganisation extends RunnerGroup {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The organisations's address.
 | 
			
		||||
   * Optional
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Address, address => address.groups, { nullable: true })
 | 
			
		||||
  address?: Address;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 * Used to link teams to runner groups.
 | 
			
		||||
 */
 | 
			
		||||
   * The organisation's teams.
 | 
			
		||||
   * Used to link teams to a organisation.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true })
 | 
			
		||||
  teams: RunnerTeam[];
 | 
			
		||||
}
 | 
			
		||||
@@ -4,14 +4,15 @@ import { RunnerGroup } from "./RunnerGroup";
 | 
			
		||||
import { RunnerOrganisation } from "./RunnerOrganisation";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a runner team (class or deparment for example).
 | 
			
		||||
 * Defines the RunnerTeam entity.
 | 
			
		||||
 * This usually is a school class or department in a company.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class RunnerTeam extends RunnerGroup {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The team's parent group.
 | 
			
		||||
   * Optional
 | 
			
		||||
   * Every team has to be part of a runnerOrganisation - this get's checked on creation and update.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerOrganisation, org => org.teams, { nullable: true })
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,8 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, TableInheritance } f
 | 
			
		||||
import { Runner } from "./Runner";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the scan interface.
 | 
			
		||||
 * Defines the Scan entity.
 | 
			
		||||
 * A scan basicly adds a certain distance to a runner's total ran distance.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
@TableInheritance({ column: { name: "type", type: "varchar" } })
 | 
			
		||||
@@ -22,7 +23,8 @@ export abstract class Scan {
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated runner.
 | 
			
		||||
   * The scan's associated runner.
 | 
			
		||||
   * This is important to link ran distances to runners.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Runner, runner => runner.scans, { nullable: false })
 | 
			
		||||
@@ -30,15 +32,17 @@ export abstract class Scan {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scan's distance in meters.
 | 
			
		||||
   * Can be set manually or derived from another object.
 | 
			
		||||
   */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
  abstract distance: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 * Is the scan valid (for fraud reasons).
 | 
			
		||||
 * Default: true
 | 
			
		||||
 */
 | 
			
		||||
   * Is the scan valid (for fraud reasons).
 | 
			
		||||
   * The determination of validity will work differently for every child class.
 | 
			
		||||
   * Default: true
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  valid: boolean = true;
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,8 @@ import { Track } from "./Track";
 | 
			
		||||
import { TrackScan } from "./TrackScan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * ScannerStations have the ability to create scans for specific tracks.
 | 
			
		||||
 * Defines the ScanStation entity.
 | 
			
		||||
 * ScanStations get used to create TrackScans for runners based on a scan of their runnerCard.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class ScanStation {
 | 
			
		||||
@@ -23,6 +24,7 @@ export class ScanStation {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The station's description.
 | 
			
		||||
   * Mostly for better UX when traceing back stuff.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
@@ -31,6 +33,7 @@ export class ScanStation {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track this station is associated with.
 | 
			
		||||
   * All scans created by this station will also be associated with this track.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Track, track => track.stations, { nullable: false })
 | 
			
		||||
@@ -38,6 +41,7 @@ export class ScanStation {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The station's api key.
 | 
			
		||||
   * This is used to authorize a station against the api (not implemented yet).
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
@@ -45,7 +49,7 @@ export class ScanStation {
 | 
			
		||||
  key: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Is the station enabled (for fraud reasons)?
 | 
			
		||||
   * Is the station enabled (for fraud and setup reasons)?
 | 
			
		||||
   * Default: true
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,6 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
 | 
			
		||||
  IsPositive,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
@@ -10,7 +9,7 @@ import { ScanStation } from "./ScanStation";
 | 
			
		||||
import { TrackScan } from "./TrackScan";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a track of given length.
 | 
			
		||||
 * Defines the Track entity.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class Track {
 | 
			
		||||
@@ -23,6 +22,7 @@ export class Track {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track's name.
 | 
			
		||||
   * Mainly here for UX.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
@@ -31,6 +31,7 @@ export class Track {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The track's length/distance in meters.
 | 
			
		||||
   * Will be used to calculate runner's ran distances.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
@@ -38,13 +39,15 @@ export class Track {
 | 
			
		||||
  distance: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link scan stations to track.
 | 
			
		||||
   * Used to link scan stations to a certain track.
 | 
			
		||||
   * This makes the configuration of the scan stations easier.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => ScanStation, station => station.track, { nullable: true })
 | 
			
		||||
  stations: ScanStation[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Used to link track scans to a track.
 | 
			
		||||
   * The scan will derive it's distance from the track's distance.
 | 
			
		||||
   */
 | 
			
		||||
  @OneToMany(() => TrackScan, scan => scan.track, { nullable: true })
 | 
			
		||||
  scans: TrackScan[];
 | 
			
		||||
 
 | 
			
		||||
@@ -12,26 +12,30 @@ import { ScanStation } from "./ScanStation";
 | 
			
		||||
import { Track } from "./Track";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the scan interface.
 | 
			
		||||
 * Defines the TrackScan entity.
 | 
			
		||||
 * A track scan usaually get's generated by a scan station.
 | 
			
		||||
*/
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class TrackScan extends Scan {
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated track.
 | 
			
		||||
   * The scan's associated track.
 | 
			
		||||
   * This is used to determine the scan's distance.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => Track, track => track.scans, { nullable: true })
 | 
			
		||||
  track: Track;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The associated card.
 | 
			
		||||
   * The runnerCard associated with the scan.
 | 
			
		||||
   * This get's saved for documentation and management purposes.
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => RunnerCard, card => card.scans, { nullable: true })
 | 
			
		||||
  card: RunnerCard;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scanning station.
 | 
			
		||||
   * The scanning station that created the scan.
 | 
			
		||||
   * Mainly used for logging and traceing back scans (or errors)
 | 
			
		||||
   */
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @ManyToOne(() => ScanStation, station => station.scans, { nullable: true })
 | 
			
		||||
@@ -39,6 +43,7 @@ export class TrackScan extends Scan {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scan's distance in meters.
 | 
			
		||||
   * This just get's loaded from it's track.
 | 
			
		||||
   */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @IsPositive()
 | 
			
		||||
@@ -48,6 +53,7 @@ export class TrackScan extends Scan {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The scan's creation timestamp.
 | 
			
		||||
   * Will be used to implement fraud detection.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsDateString()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,36 @@
 | 
			
		||||
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUUID } from "class-validator";
 | 
			
		||||
import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm";
 | 
			
		||||
import { config } from '../../config';
 | 
			
		||||
import { Permission } from './Permission';
 | 
			
		||||
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
 | 
			
		||||
import { ResponseUser } from '../responses/ResponseUser';
 | 
			
		||||
import { Principal } from './Principal';
 | 
			
		||||
import { UserAction } from './UserAction';
 | 
			
		||||
import { UserGroup } from './UserGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a admin user.
 | 
			
		||||
 * Defines the User entity.
 | 
			
		||||
 * Users are the ones that can use the "admin" webui and do stuff in the backend.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class User {
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class User extends Principal {
 | 
			
		||||
  /**
 | 
			
		||||
  * autogenerated unique id (primary key).
 | 
			
		||||
  */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * uuid
 | 
			
		||||
  * The user's uuid.
 | 
			
		||||
  * Mainly gets used as a per-user salt for the password hash.
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ unique: true })
 | 
			
		||||
  @IsUUID(4)
 | 
			
		||||
  uuid: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * user email
 | 
			
		||||
  * The user's e-mail address.
 | 
			
		||||
  * Either username or email has to be set (otherwise the user couldn't log in).
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @IsEmail()
 | 
			
		||||
  email?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * user phone
 | 
			
		||||
  * The user's phone number.
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
@@ -40,14 +38,15 @@ export class User {
 | 
			
		||||
  phone?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * username
 | 
			
		||||
  * The user's username.
 | 
			
		||||
  * Either username or email has to be set (otherwise the user couldn't log in).
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  username?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * firstname
 | 
			
		||||
  * The user's first name.
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
@@ -55,7 +54,7 @@ export class User {
 | 
			
		||||
  firstname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * middlename
 | 
			
		||||
  * The user's middle name.
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsString()
 | 
			
		||||
@@ -63,7 +62,7 @@ export class User {
 | 
			
		||||
  middlename?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * lastname
 | 
			
		||||
  * The user's last name.
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
@@ -71,7 +70,8 @@ export class User {
 | 
			
		||||
  lastname: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * password
 | 
			
		||||
  * The user's password.
 | 
			
		||||
  * This is a argon2 hash salted with the user's uuid.
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsString()
 | 
			
		||||
@@ -79,14 +79,8 @@ export class User {
 | 
			
		||||
  password: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * permissions
 | 
			
		||||
  */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToOne(() => Permission, permission => permission.users, { nullable: true })
 | 
			
		||||
  permissions?: Permission[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * groups
 | 
			
		||||
  * The groups this user is a part of.
 | 
			
		||||
  * The user will inherit the groups permissions (without overwriting his own).
 | 
			
		||||
  */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @ManyToMany(() => UserGroup, { nullable: true })
 | 
			
		||||
@@ -94,49 +88,50 @@ export class User {
 | 
			
		||||
  groups: UserGroup[];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * is user enabled?
 | 
			
		||||
  * Is this user enabled?
 | 
			
		||||
  */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsBoolean()
 | 
			
		||||
  enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * jwt refresh count
 | 
			
		||||
  * The user's jwt refresh token count.
 | 
			
		||||
  * Used to invalidate jwts.
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  @Column({ default: 1 })
 | 
			
		||||
  refreshTokenCount?: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * profilepic
 | 
			
		||||
  * The user's profile picture.
 | 
			
		||||
  * We haven't decided yet if this will be a bas64 encoded image or just a link to the profile picture.
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: true })
 | 
			
		||||
  @Column({ nullable: true, unique: false })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  profilePic?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * actions
 | 
			
		||||
  * The last time the user requested a password reset.
 | 
			
		||||
  * Used to prevent spamming of the password reset route.
 | 
			
		||||
  */
 | 
			
		||||
  @Column({ nullable: true, unique: false })
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  resetRequestedTimestamp?: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The actions performed by this user.
 | 
			
		||||
   * For documentation purposes only, will be implemented later.
 | 
			
		||||
   */
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @OneToMany(() => UserAction, action => action.user, { nullable: true })
 | 
			
		||||
  actions: UserAction[]
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * calculate all permissions
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public get calc_permissions(): Permission[] {
 | 
			
		||||
    let final_permissions = []
 | 
			
		||||
    this.groups.forEach((permission) => {
 | 
			
		||||
      if (!final_permissions.includes(permission)) {
 | 
			
		||||
        final_permissions.push(permission)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    this.permissions.forEach((permission) => {
 | 
			
		||||
      if (!final_permissions.includes(permission)) {
 | 
			
		||||
        final_permissions.push(permission)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    return final_permissions
 | 
			
		||||
  public toResponse(): ResponsePrincipal {
 | 
			
		||||
    return new ResponseUser(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsEnum,
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { PermissionAction } from '../enums/PermissionAction';
 | 
			
		||||
import { User } from './User';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the UserAction interface.
 | 
			
		||||
 * Defines the UserAction entity.
 | 
			
		||||
 * Will later be used to document a user's actions.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class UserAction {
 | 
			
		||||
@@ -20,7 +23,7 @@ export class UserAction {
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * user
 | 
			
		||||
   * The user that performed the action.
 | 
			
		||||
   */
 | 
			
		||||
  @ManyToOne(() => User, user => user.actions)
 | 
			
		||||
  user: User
 | 
			
		||||
@@ -34,15 +37,16 @@ export class UserAction {
 | 
			
		||||
  target: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The actions's action (e.g. UPDATE)
 | 
			
		||||
   * The actions's action (e.g. UPDATE).
 | 
			
		||||
   * Directly pulled from the PermissionAction Enum.
 | 
			
		||||
   */
 | 
			
		||||
  @Column()
 | 
			
		||||
  @IsNotEmpty()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  action: string;
 | 
			
		||||
  @Column({ type: 'varchar' })
 | 
			
		||||
  @IsEnum(PermissionAction)
 | 
			
		||||
  action: PermissionAction;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The description of change (before-> after; e.g. distance:15->17)
 | 
			
		||||
   * The description of the change (before-> after; e.g. distance:15->17).
 | 
			
		||||
   * Will later be defined in more detail.
 | 
			
		||||
   */
 | 
			
		||||
  @Column({ nullable: true })
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,19 @@
 | 
			
		||||
import {
 | 
			
		||||
  IsInt,
 | 
			
		||||
  IsNotEmpty,
 | 
			
		||||
  IsOptional,
 | 
			
		||||
  IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
 | 
			
		||||
import { Permission } from "./Permission";
 | 
			
		||||
import { ChildEntity, Column } from "typeorm";
 | 
			
		||||
import { ResponsePrincipal } from '../responses/ResponsePrincipal';
 | 
			
		||||
import { ResponseUserGroup } from '../responses/ResponseUserGroup';
 | 
			
		||||
import { Principal } from './Principal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the UserGroup interface.
 | 
			
		||||
 * Defines the UserGroup entity.
 | 
			
		||||
 * This entity describes a group of users with a set of permissions.
 | 
			
		||||
*/
 | 
			
		||||
@Entity()
 | 
			
		||||
export class UserGroup {
 | 
			
		||||
  /**
 | 
			
		||||
   * Autogenerated unique id (primary key).
 | 
			
		||||
   */
 | 
			
		||||
  @PrimaryGeneratedColumn()
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
     * permissions
 | 
			
		||||
     */
 | 
			
		||||
  @ManyToOne(() => Permission, permission => permission.groups, { nullable: true })
 | 
			
		||||
  permissions: Permission[];
 | 
			
		||||
@ChildEntity()
 | 
			
		||||
export class UserGroup extends Principal {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The group's name
 | 
			
		||||
@@ -40,4 +30,11 @@ export class UserGroup {
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  @IsString()
 | 
			
		||||
  description?: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Turns this entity into it's response class.
 | 
			
		||||
   */
 | 
			
		||||
  public toResponse(): ResponsePrincipal {
 | 
			
		||||
    return new ResponseUserGroup(this);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/models/enums/PermissionAction.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/models/enums/PermissionAction.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This enum contains all posible actions for permissions.
 | 
			
		||||
 */
 | 
			
		||||
export enum PermissionAction {
 | 
			
		||||
    GET = 'GET',
 | 
			
		||||
    CREATE = 'CREATE',
 | 
			
		||||
    UPDATE = 'UPDATE',
 | 
			
		||||
    DELETE = 'DELETE',
 | 
			
		||||
    IMPORT = 'IMPORT'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/models/enums/PermissionTargets.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/models/enums/PermissionTargets.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
/**
 | 
			
		||||
 * This enum contains all posible targets for permissions.
 | 
			
		||||
 */
 | 
			
		||||
export enum PermissionTarget {
 | 
			
		||||
    RUNNER = 'RUNNER',
 | 
			
		||||
    ORGANISATION = 'ORGANISATION',
 | 
			
		||||
    TEAM = 'TEAM',
 | 
			
		||||
    TRACK = 'TRACK',
 | 
			
		||||
    USER = 'USER',
 | 
			
		||||
    USERGROUP = 'USERGROUP',
 | 
			
		||||
    PERMISSION = 'PERMISSION'
 | 
			
		||||
}
 | 
			
		||||
@@ -1,26 +1,29 @@
 | 
			
		||||
import { IsInt, IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a auth object
 | 
			
		||||
 * Defines the repsonse auth.
 | 
			
		||||
*/
 | 
			
		||||
export class Auth {
 | 
			
		||||
  /**
 | 
			
		||||
  * access_token - JWT shortterm access token
 | 
			
		||||
  * The access_token - JWT shortterm access token.
 | 
			
		||||
  */
 | 
			
		||||
  @IsString()
 | 
			
		||||
  access_token: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * refresh_token - longterm refresh token (used for requesting new access tokens)
 | 
			
		||||
  * The refresh_token - longterm refresh token (used for requesting new access tokens).
 | 
			
		||||
  */
 | 
			
		||||
  @IsString()
 | 
			
		||||
  refresh_token: string;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * access_token_expires_at - unix timestamp of access token expiry
 | 
			
		||||
  * The unix timestamp for access the token's expiry.
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  access_token_expires_at: number;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
  * refresh_token_expires_at - unix timestamp of access token expiry
 | 
			
		||||
  * The unix unix timestamp for the access token's expiry.
 | 
			
		||||
  */
 | 
			
		||||
  @IsInt()
 | 
			
		||||
  refresh_token_expires_at: number;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a empty response object
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseEmpty {
 | 
			
		||||
}
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a empty response object.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseEmpty {
 | 
			
		||||
    @IsString()
 | 
			
		||||
    response: string = "nothing here"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
import { IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a Logout object
 | 
			
		||||
 * Defines the logout response.
 | 
			
		||||
*/
 | 
			
		||||
export class Logout {
 | 
			
		||||
  /**
 | 
			
		||||
  * timestamp of logout
 | 
			
		||||
  * The logout's timestamp.
 | 
			
		||||
  */
 | 
			
		||||
  @IsString()
 | 
			
		||||
  timestamp: number;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,15 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsInt,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { IsInt, IsString } from "class-validator";
 | 
			
		||||
import { Participant } from '../entities/Participant';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a participant response.
 | 
			
		||||
 * Defines the participant response.
 | 
			
		||||
*/
 | 
			
		||||
export abstract class ResponseParticipant {
 | 
			
		||||
    /**
 | 
			
		||||
     * Autogenerated unique id (primary key).
 | 
			
		||||
     * The participant's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;;
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's first name.
 | 
			
		||||
@@ -25,7 +19,6 @@ export abstract class ResponseParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's middle name.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
@@ -38,18 +31,20 @@ export abstract class ResponseParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's phone number.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The participant's e-mail address.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseParticipant object from a participant.
 | 
			
		||||
     * @param participant The participant the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(participant: Participant) {
 | 
			
		||||
        this.id = participant.id;
 | 
			
		||||
        this.firstname = participant.firstname;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								src/models/responses/ResponsePermission.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/models/responses/ResponsePermission.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsEnum,
 | 
			
		||||
    IsInt,
 | 
			
		||||
    IsNotEmpty,
 | 
			
		||||
    IsObject
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Permission } from '../entities/Permission';
 | 
			
		||||
import { PermissionAction } from '../enums/PermissionAction';
 | 
			
		||||
import { PermissionTarget } from '../enums/PermissionTargets';
 | 
			
		||||
import { ResponsePrincipal } from './ResponsePrincipal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the permission response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponsePermission {
 | 
			
		||||
    /**
 | 
			
		||||
     * The permission's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The permissions's principal.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    principal: ResponsePrincipal;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The permissions's target.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsEnum(PermissionTarget)
 | 
			
		||||
    target: PermissionTarget;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The permissions's action.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsEnum(PermissionAction)
 | 
			
		||||
    action: PermissionAction;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponsePermission object from a permission.
 | 
			
		||||
     * @param permission The permission the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(permission: Permission) {
 | 
			
		||||
        this.id = permission.id;
 | 
			
		||||
        this.principal = permission.principal.toResponse();
 | 
			
		||||
        this.target = permission.target;
 | 
			
		||||
        this.action = permission.action;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/models/responses/ResponsePrincipal.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/models/responses/ResponsePrincipal.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsInt
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Principal } from '../entities/Principal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the principal response.
 | 
			
		||||
*/
 | 
			
		||||
export abstract class ResponsePrincipal {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The principal's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponsePrincipal object from a principal.
 | 
			
		||||
     * @param principal The principal the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(principal: Principal) {
 | 
			
		||||
        this.id = principal.id;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,13 +7,12 @@ import { RunnerGroup } from '../entities/RunnerGroup';
 | 
			
		||||
import { ResponseParticipant } from './ResponseParticipant';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines RunnerTeam's response class.
 | 
			
		||||
 * Defines the runner response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseRunner extends ResponseParticipant {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The runner's currently ran distance in meters.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    distance: number;
 | 
			
		||||
@@ -24,6 +23,10 @@ export class ResponseRunner extends ResponseParticipant {
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    group: RunnerGroup;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseRunner object from a runner.
 | 
			
		||||
     * @param runner The user the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(runner: Runner) {
 | 
			
		||||
        super(runner);
 | 
			
		||||
        this.distance = runner.scans.filter(scan => { scan.valid === true }).reduce((sum, current) => sum + current.distance, 0);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,38 +1,20 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsInt,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    IsNotEmpty,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    IsObject,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    IsOptional,
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { IsInt, IsNotEmpty, IsObject, IsOptional, IsString } from "class-validator";
 | 
			
		||||
import { GroupContact } from '../entities/GroupContact';
 | 
			
		||||
import { RunnerGroup } from '../entities/RunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a track of given length.
 | 
			
		||||
 * Defines the runnerGroup response.
 | 
			
		||||
*/
 | 
			
		||||
export abstract class ResponseRunnerGroup {
 | 
			
		||||
    /**
 | 
			
		||||
     * Autogenerated unique id (primary key).
 | 
			
		||||
     * The runnerGroup's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    id: number;;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The groups's name.
 | 
			
		||||
     * The runnerGroup's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
@@ -40,13 +22,16 @@ export abstract class ResponseRunnerGroup {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The group's contact.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     * The runnerGroup's contact.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    contact?: GroupContact;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseRunnerGroup object from a runnerGroup.
 | 
			
		||||
     * @param group The runnerGroup the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(group: RunnerGroup) {
 | 
			
		||||
        this.id = group.id;
 | 
			
		||||
        this.name = group.name;
 | 
			
		||||
 
 | 
			
		||||
@@ -9,26 +9,27 @@ import { RunnerTeam } from '../entities/RunnerTeam';
 | 
			
		||||
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines RunnerOrgs's response class.
 | 
			
		||||
 * Defines the runnerOrganisation response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseRunnerOrganisation extends ResponseRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The orgs's address.
 | 
			
		||||
     * Optional.
 | 
			
		||||
     * The runnerOrganisation's address.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    address?: Address;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The orgs associated teams.
 | 
			
		||||
     * The runnerOrganisation associated teams.
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsArray()
 | 
			
		||||
    teams: RunnerTeam[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseRunnerOrganisation object from a runnerOrganisation.
 | 
			
		||||
     * @param org The runnerOrganisation the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(org: RunnerOrganisation) {
 | 
			
		||||
        super(org);
 | 
			
		||||
        this.address = org.address;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,24 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsNotEmpty,
 | 
			
		||||
    IsObject
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { IsNotEmpty, IsObject } from "class-validator";
 | 
			
		||||
import { RunnerOrganisation } from '../entities/RunnerOrganisation';
 | 
			
		||||
import { RunnerTeam } from '../entities/RunnerTeam';
 | 
			
		||||
import { ResponseRunnerGroup } from './ResponseRunnerGroup';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines RunnerTeam's response class.
 | 
			
		||||
 * Defines the runnerTeam response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseRunnerTeam extends ResponseRunnerGroup {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The team's parent group (organisation).
 | 
			
		||||
     * Optional.
 | 
			
		||||
     * The runnerTeam's parent group (organisation).
 | 
			
		||||
     */
 | 
			
		||||
    @IsObject()
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    parentGroup: RunnerOrganisation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseRunnerTeam object from a runnerTeam.
 | 
			
		||||
     * @param team The team the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(team: RunnerTeam) {
 | 
			
		||||
        super(team);
 | 
			
		||||
        this.parentGroup = team.parentGroup;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,12 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsInt,
 | 
			
		||||
 | 
			
		||||
    IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { IsInt, IsString } from "class-validator";
 | 
			
		||||
import { Track } from '../entities/Track';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a track of given length.
 | 
			
		||||
 * Defines the track response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseTrack {
 | 
			
		||||
    /**
 | 
			
		||||
     * Autogenerated unique id (primary key).
 | 
			
		||||
     * The track's id.
 | 
			
		||||
     */
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    id: number;;
 | 
			
		||||
@@ -27,6 +23,10 @@ export class ResponseTrack {
 | 
			
		||||
    @IsInt()
 | 
			
		||||
    distance: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseTrack object from a track.
 | 
			
		||||
     * @param track The track the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(track: Track) {
 | 
			
		||||
        this.id = track.id;
 | 
			
		||||
        this.name = track.name;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								src/models/responses/ResponseUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/models/responses/ResponseUser.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
import {
 | 
			
		||||
    IsArray,
 | 
			
		||||
    IsBoolean,
 | 
			
		||||
 | 
			
		||||
    IsOptional,
 | 
			
		||||
    IsString
 | 
			
		||||
} from "class-validator";
 | 
			
		||||
import { Permission } from '../entities/Permission';
 | 
			
		||||
import { User } from '../entities/User';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
import { ResponsePrincipal } from './ResponsePrincipal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the user response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseUser extends ResponsePrincipal {
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's first name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    firstname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's middle name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    middlename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's last name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    lastname: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's phone number.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    phone?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's e-mail address.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    email?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's username.
 | 
			
		||||
     */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    username?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * Is user enabled?
 | 
			
		||||
    */
 | 
			
		||||
    @IsBoolean()
 | 
			
		||||
    enabled: boolean = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
    * The user's profile pic.
 | 
			
		||||
    */
 | 
			
		||||
    @IsString()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    profilePic?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The groups that the user is a part of.
 | 
			
		||||
     */
 | 
			
		||||
    @IsArray()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    groups: UserGroup[];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user's permissions.
 | 
			
		||||
     */
 | 
			
		||||
    @IsArray()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    permissions: Permission[];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseUser object from a user.
 | 
			
		||||
     * @param user The user the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(user: User) {
 | 
			
		||||
        super(user);
 | 
			
		||||
        this.firstname = user.firstname;
 | 
			
		||||
        this.middlename = user.middlename;
 | 
			
		||||
        this.lastname = user.lastname;
 | 
			
		||||
        this.phone = user.phone;
 | 
			
		||||
        this.email = user.email;
 | 
			
		||||
        this.username = user.username;
 | 
			
		||||
        this.enabled = user.enabled;
 | 
			
		||||
        this.profilePic = user.profilePic;
 | 
			
		||||
        this.groups = user.groups;
 | 
			
		||||
        this.permissions = user.permissions;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								src/models/responses/ResponseUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/models/responses/ResponseUserGroup.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import { IsArray, IsNotEmpty, IsOptional, IsString } from "class-validator";
 | 
			
		||||
import { Permission } from '../entities/Permission';
 | 
			
		||||
import { UserGroup } from '../entities/UserGroup';
 | 
			
		||||
import { ResponsePrincipal } from './ResponsePrincipal';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the userGroup response.
 | 
			
		||||
*/
 | 
			
		||||
export class ResponseUserGroup extends ResponsePrincipal {
 | 
			
		||||
    /**
 | 
			
		||||
     * The userGroup's name.
 | 
			
		||||
     */
 | 
			
		||||
    @IsNotEmpty()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    name: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The userGroup's description.
 | 
			
		||||
     */
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    @IsString()
 | 
			
		||||
    description?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The userGroup's permissions.
 | 
			
		||||
     */
 | 
			
		||||
    @IsArray()
 | 
			
		||||
    @IsOptional()
 | 
			
		||||
    permissions: Permission[];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a ResponseUserGroup object from a userGroup.
 | 
			
		||||
     * @param group The userGroup the response shall be build for.
 | 
			
		||||
     */
 | 
			
		||||
    public constructor(group: UserGroup) {
 | 
			
		||||
        super(group);
 | 
			
		||||
        this.name = group.name;
 | 
			
		||||
        this.description = group.description;
 | 
			
		||||
        this.permissions = group.permissions;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										63
									
								
								src/openapi_export.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/openapi_export.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,63 @@
 | 
			
		||||
import { validationMetadatasToSchemas } from 'class-validator-jsonschema';
 | 
			
		||||
import consola from "consola";
 | 
			
		||||
import fs from "fs";
 | 
			
		||||
import "reflect-metadata";
 | 
			
		||||
import { createExpressServer, getMetadataArgsStorage } from "routing-controllers";
 | 
			
		||||
import { routingControllersToSpec } from 'routing-controllers-openapi';
 | 
			
		||||
import authchecker from "./authchecker";
 | 
			
		||||
import { config } from './config';
 | 
			
		||||
import { ErrorHandler } from './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}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const storage = getMetadataArgsStorage();
 | 
			
		||||
const schemas = validationMetadatasToSchemas({
 | 
			
		||||
    refPointerPrefix: "#/components/schemas/",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
//Spec creation based on the previously created schemas
 | 
			
		||||
const spec = routingControllersToSpec(
 | 
			
		||||
    storage,
 | 
			
		||||
    {
 | 
			
		||||
        routePrefix: "/api"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        components: {
 | 
			
		||||
            schemas,
 | 
			
		||||
            "securitySchemes": {
 | 
			
		||||
                "AuthToken": {
 | 
			
		||||
                    "type": "http",
 | 
			
		||||
                    "scheme": "bearer",
 | 
			
		||||
                    "bearerFormat": "JWT",
 | 
			
		||||
                    description: "A JWT based access token. Use /api/auth/login or /api/auth/refresh to get one."
 | 
			
		||||
                },
 | 
			
		||||
                "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."
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        info: {
 | 
			
		||||
            description: "The the backend API for the LfK! runner system.",
 | 
			
		||||
            title: "LfK! Backend API",
 | 
			
		||||
            version: "0.0.5",
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
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");
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								src/seeds/SeedUsers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/seeds/SeedUsers.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
import { Connection } from 'typeorm';
 | 
			
		||||
import { Factory, Seeder } from 'typeorm-seeding';
 | 
			
		||||
import { CreatePermission } from '../models/actions/CreatePermission';
 | 
			
		||||
import { CreateUser } from '../models/actions/CreateUser';
 | 
			
		||||
import { CreateUserGroup } from '../models/actions/CreateUserGroup';
 | 
			
		||||
import { Permission } from '../models/entities/Permission';
 | 
			
		||||
import { User } from '../models/entities/User';
 | 
			
		||||
import { UserGroup } from '../models/entities/UserGroup';
 | 
			
		||||
import { PermissionAction } from '../models/enums/PermissionAction';
 | 
			
		||||
import { PermissionTarget } from '../models/enums/PermissionTargets';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Seeds a admin group with a demo user into the database for initial setup and auto recovery.
 | 
			
		||||
 * We know that the nameing isn't perfectly fitting. Feel free to change it.
 | 
			
		||||
 */
 | 
			
		||||
export default class SeedUsers implements Seeder {
 | 
			
		||||
    public async run(factory: Factory, connection: Connection): Promise<any> {
 | 
			
		||||
        let adminGroup: UserGroup = await this.createAdminGroup(connection);
 | 
			
		||||
        await this.createUser(connection, adminGroup.id);
 | 
			
		||||
        await this.createPermissions(connection, adminGroup.id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async createAdminGroup(connection: Connection) {
 | 
			
		||||
        let adminGroup = new CreateUserGroup();
 | 
			
		||||
        adminGroup.name = "ADMINS";
 | 
			
		||||
        adminGroup.description = "Have all possible permissions";
 | 
			
		||||
        return await connection.getRepository(UserGroup).save(await adminGroup.toUserGroup());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async createUser(connection: Connection, group: number) {
 | 
			
		||||
        let initialUser = new CreateUser();
 | 
			
		||||
        initialUser.firstname = "demo";
 | 
			
		||||
        initialUser.lastname = "demo";
 | 
			
		||||
        initialUser.username = "demo";
 | 
			
		||||
        initialUser.password = "demo";
 | 
			
		||||
        initialUser.groups = group;
 | 
			
		||||
        return await connection.getRepository(User).save(await initialUser.toUser());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async createPermissions(connection: Connection, principal: number) {
 | 
			
		||||
        let repo = await connection.getRepository(Permission);
 | 
			
		||||
        for (let target in PermissionTarget) {
 | 
			
		||||
            for (let action in PermissionAction) {
 | 
			
		||||
                let permission = new CreatePermission;
 | 
			
		||||
                permission.target = <PermissionTarget>target;
 | 
			
		||||
                permission.action = <PermissionAction>action;
 | 
			
		||||
                permission.principal = principal;
 | 
			
		||||
                await repo.save(await permission.toPermission());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user