diff --git a/.env.example b/.env.example
index 076d8cd..565a391 100644
--- a/.env.example
+++ b/.env.example
@@ -1,9 +1,10 @@
APP_PORT=4010
-DB_TYPE=bla
+DB_TYPE=sqlite
DB_HOST=bla
DB_PORT=bla
DB_USER=bla
DB_PASSWORD=bla
-DB_NAME=bla
+DB_NAME=./test.sqlite
NODE_ENV=production
-POSTALCODE_COUNTRYCODE=DE
\ No newline at end of file
+POSTALCODE_COUNTRYCODE=DE
+SEED_TEST_DATA=false
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99d01f0..07cca56 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,45 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
+#### [v0.2.1](https://git.odit.services/lfk/backend/compare/v0.2.0...v0.2.1)
+
+- Created a donation runner response class for the runner selfservice [`88a7089`](https://git.odit.services/lfk/backend/commit/88a7089289e35be4468cb952b311fcb15c54c5a1)
+- Readme reorganisation [skip ci] [`e2ec0a3`](https://git.odit.services/lfk/backend/commit/e2ec0a3b64a7388ae85d557dfb66354d70cd1b72)
+- Added a seeder for runner test data [`9df9d9a`](https://git.odit.services/lfk/backend/commit/9df9d9ae80277d5ccc753639badb48c4afb13088)
+- Created a donation respoinse class for the runner selfservice [`b89f7ac`](https://git.odit.services/lfk/backend/commit/b89f7ac1b4ddd6e53e6e2e8330c1fa2170b48591)
+- Added barebones controller for the runner info selfservice [`2274b47`](https://git.odit.services/lfk/backend/commit/2274b476d6caa1de91bb13b6944f8dc233cf446e)
+- Implemented a method for getting the runner object from a jwt [`8079769`](https://git.odit.services/lfk/backend/commit/80797698818f456c7746523d5a4f66267fdab10d)
+- Added key-value like db table for config flags [`b15967f`](https://git.odit.services/lfk/backend/commit/b15967ff3162e9fe3a634a6f4fc5669f2314cc21)
+- Added a /runners/id/scans endpoint [`a82fc0f`](https://git.odit.services/lfk/backend/commit/a82fc0fb9e9c3cbdc6be299b27164c0811e58775)
+- Now creating a test contact [`1837336`](https://git.odit.services/lfk/backend/commit/1837336865893ca39d3bc628ff3c57e018a8555d)
+- 🧾New changelog file version [CI SKIP] [skip ci] [`02677de`](https://git.odit.services/lfk/backend/commit/02677de5c07b2ac5dcff5567655130ba1b1d48cf)
+- The data seeding now only get's triggered on the first time thx to using the key-value [`7bc6030`](https://git.odit.services/lfk/backend/commit/7bc603028dc60d26ffc5327868afbce512966d4d)
+- 🧾New changelog file version [CI SKIP] [skip ci] [`3a93c9c`](https://git.odit.services/lfk/backend/commit/3a93c9c078af38ba837b55bf4590867dfd401955)
+- Added a "onlyValid" query param [`b5f3dec`](https://git.odit.services/lfk/backend/commit/b5f3dec93bfe4180abbe9ce74094cb1269d0e686)
+- Added a citizen org seeder [`2db6510`](https://git.odit.services/lfk/backend/commit/2db6510a8ad83300b286a3bd35ca4db103da72d1)
+- 🧾New changelog file version [CI SKIP] [skip ci] [`d528134`](https://git.odit.services/lfk/backend/commit/d5281348b6f3bd6f2e6936ee4497860699b8c3c6)
+- 📖New license file version [CI SKIP] [skip ci] [`d8b6669`](https://git.odit.services/lfk/backend/commit/d8b6669d126e64d9e434b5f841ae17a02117822b)
+- Added get tests for the /runner/scans endpoint [`26dff4f`](https://git.odit.services/lfk/backend/commit/26dff4f41829e8571231aff3c5d0e3a7c53559d8)
+- Beautified import [`c5f7cb2`](https://git.odit.services/lfk/backend/commit/c5f7cb2c68dbee0ab1e0361754f4d4b876666c82)
+- Added sqlite as to env.sample db of choice [skip ci] [`f4668b6`](https://git.odit.services/lfk/backend/commit/f4668b6e81d7aeac62e24291ffcb39b00ea44aac)
+- 🚀Bumped version to v0.2.1 [`6de9d54`](https://git.odit.services/lfk/backend/commit/6de9d547b736c4538dac5254353d483576337290)
+- Merge pull request 'Runner scans endpoint feature/113-runner_scans' (#116) from feature/113-runner_scans into dev [`36d01a0`](https://git.odit.services/lfk/backend/commit/36d01a0a890eb74428679ec6c4fcb14708aaa9fe)
+- Merge pull request 'Runner selfservice info endpoint feature/111-runner_selfservic_info' (#115) from feature/111-runner_selfservic_info into dev [`1717df1`](https://git.odit.services/lfk/backend/commit/1717df113edeab2d2628041ee1eccc27380fd379)
+- Merge pull request 'Implemented more seeding feature/110-seeding' (#114) from feature/110-seeding into dev [`886c109`](https://git.odit.services/lfk/backend/commit/886c1092d60f8e39357e3b841ed01bb082ede2c4)
+- Implemented the get part of the runner selfservice (no jwts are availdable yet (tm) [`da1fe34`](https://git.odit.services/lfk/backend/commit/da1fe34249a741115c1aeedcade16c5c852e896b)
+- Fixed the bool converter for null values [`e12aedd`](https://git.odit.services/lfk/backend/commit/e12aedd1aad6de1f934e9593dda4607a303b2eb5)
+- Added a config option for test data seeding [`67ba489`](https://git.odit.services/lfk/backend/commit/67ba489fe2f2a2706d640a668cd0e675ded6a7df)
+- SEED_TEST_DATA is now false by default [`8870ebd`](https://git.odit.services/lfk/backend/commit/8870ebdb5e6d9045222440abc2c047929a74b520)
+- Updated the openapi description [`1915697`](https://git.odit.services/lfk/backend/commit/191569792c9a5cee93718555bba4e7679e4391af)
+- Fixed wrong amount calculation [`4ee8079`](https://git.odit.services/lfk/backend/commit/4ee807973e1995681ec549f7c482bc5514a6ec55)
+- Added bool conversion for testdata seeding env var [`c18012f`](https://git.odit.services/lfk/backend/commit/c18012f65a704e07acd56870c9ed9f6d06cf97a9)
+- Now also seeding runners to the test org [`eab0e63`](https://git.odit.services/lfk/backend/commit/eab0e634a26c1a80e7fa2ccb9dc368f0760b2fd8)
+
#### [v0.2.0](https://git.odit.services/lfk/backend/compare/v0.1.1...v0.2.0)
-- 🧾New changelog file version [CI SKIP] [skip ci] [`8960aa5`](https://git.odit.services/lfk/backend/commit/8960aa5545ddeb57d4ef42c21c0ca6001dfeaea9)
-- 🚀Bumped version to v0.2.0 [`ddafd90`](https://git.odit.services/lfk/backend/commit/ddafd90d3e41fb9ee37172a8306c30d8483dfe2c)
-- Merge pull request 'Implemented group contacts feature/104-contacts' (#108) from feature/104-contacts into dev [`a0c2b5a`](https://git.odit.services/lfk/backend/commit/a0c2b5ade8d198ec16d33b39e47205e8b03a669f)
+> 20 January 2021
+
+- Merge pull request 'Alpha Release 0.2.0' (#109) from dev into main [`dd3d93e`](https://git.odit.services/lfk/backend/commit/dd3d93edc7db7ca7f133cb2d8f60c3eaf30bcbf0)
- Updated contact update tests [`c3d008e`](https://git.odit.services/lfk/backend/commit/c3d008ec0ff92f80addbdb93ffc1fa2b3278a8a6)
- Added contact delete tests [`dd7e5da`](https://git.odit.services/lfk/backend/commit/dd7e5dae368a8decd79357f658dda2164fa6f1e7)
- Added contact add valid tests [`e165f01`](https://git.odit.services/lfk/backend/commit/e165f019307e7745357493eacf3e2fa31538122b)
@@ -29,6 +63,7 @@ All notable changes to this project will be documented in this file. Dates are d
- Implemented contact updateing [`28fb983`](https://git.odit.services/lfk/backend/commit/28fb9834e18bde012c5b51cc49a39585d20f7cc1)
- Fixed key null constraint [`de82437`](https://git.odit.services/lfk/backend/commit/de824375d3a1da6ee4d78ea39b7da66fc05f2a02)
- Implemented contact posting [`11af9c0`](https://git.odit.services/lfk/backend/commit/11af9c02d977dcd6919652256dbdb9fd5438cabd)
+- 🧾New changelog file version [CI SKIP] [skip ci] [`8960aa5`](https://git.odit.services/lfk/backend/commit/8960aa5545ddeb57d4ef42c21c0ca6001dfeaea9)
- Implemented contact group setting on creation [`3b06d1a`](https://git.odit.services/lfk/backend/commit/3b06d1a6ef3c95eb5bb7d485accddabba0a8e4f7)
- 🧾New changelog file version [CI SKIP] [skip ci] [`32e054e`](https://git.odit.services/lfk/backend/commit/32e054eb84c869210fd483583ae5a6d0e2249cf9)
- Switched Address to embedded entity [`7fbe649`](https://git.odit.services/lfk/backend/commit/7fbe649dc90f4bb9f240c5a80fed447048e5e105)
@@ -39,11 +74,14 @@ All notable changes to this project will be documented in this file. Dates are d
- Fixed donor address check [`4824547`](https://git.odit.services/lfk/backend/commit/4824547dde4d7f90e9e2377a26df34cabf082fdb)
- Updated contact delete tests [`8ae53f1`](https://git.odit.services/lfk/backend/commit/8ae53f1c4930e2fd72eb230a5314336f3a45a611)
- Added address to contact response [`09e429f`](https://git.odit.services/lfk/backend/commit/09e429fc676c7dd370bba0495b072f81867bd250)
-- Updated comments [`a4e8311`](https://git.odit.services/lfk/backend/commit/a4e8311cbd22588ecb4dc2fdbe05397b07d336f8)
- Updated the responseclasses to use the new address implementation [`dafac06`](https://git.odit.services/lfk/backend/commit/dafac06bc84d1b237096a561b3adcd3ca5cb1dd8)
+- Added address validity check [`ae7c5ff`](https://git.odit.services/lfk/backend/commit/ae7c5ff0c387e9337d01a9dd819a4dddc208f6dd)
+- 🧾New changelog file version [CI SKIP] [skip ci] [`da9a359`](https://git.odit.services/lfk/backend/commit/da9a3592510eacd2d67a127dc61e954343e0444b)
+- 🚀Bumped version to v0.2.0 [`ddafd90`](https://git.odit.services/lfk/backend/commit/ddafd90d3e41fb9ee37172a8306c30d8483dfe2c)
+- Merge pull request 'Implemented group contacts feature/104-contacts' (#108) from feature/104-contacts into dev [`a0c2b5a`](https://git.odit.services/lfk/backend/commit/a0c2b5ade8d198ec16d33b39e47205e8b03a669f)
+- Updated comments [`a4e8311`](https://git.odit.services/lfk/backend/commit/a4e8311cbd22588ecb4dc2fdbe05397b07d336f8)
- Removed (now useless) relations [`673dea2`](https://git.odit.services/lfk/backend/commit/673dea2e5754e99ff77f7556d4fc03d4cca28a94)
- Added missing id property [`6b4b16c`](https://git.odit.services/lfk/backend/commit/6b4b16c13b0c2f55745ded3431cad2f4986be296)
-- Added address validity check [`ae7c5ff`](https://git.odit.services/lfk/backend/commit/ae7c5ff0c387e9337d01a9dd819a4dddc208f6dd)
- 🧾New changelog file version [CI SKIP] [skip ci] [`f53894b`](https://git.odit.services/lfk/backend/commit/f53894b16ac1c06ecbeeb0b63a56ac438b2fbe1b)
- Updated comments [`8bc01d3`](https://git.odit.services/lfk/backend/commit/8bc01d3f2406ce8e58c2ab2963c858495c510dcf)
- Fixed contact cascading [`179c2a5`](https://git.odit.services/lfk/backend/commit/179c2a5157fca036acf8d0e6a51821d377860bc1)
diff --git a/README.md b/README.md
index f974523..985a69f 100644
--- a/README.md
+++ b/README.md
@@ -2,20 +2,18 @@
Backend Server
+## Quickstart 🐳
+> Use this to run the backend with a postgresql db in docker
+
+1. Clone the repo or copy the docker-compose
+2. Run in toe folder that contains the docker-compose file: `docker-compose up -d`
+3. Visit http://127.0.0.1:4010/api/docs to check if the server is running
+4. You can now use the default admin user (`demo:demo`)
+
## Dev Setup 🛠
+> Local dev setup utilizing sqlite3 as the database.
-### Local w/ sqlite
-
-1. Create a .env file in the project root containing:
- ```
- APP_PORT=4010
- DB_TYPE=sqlite
- DB_HOST=bla
- DB_PORT=bla
- DB_USER=bla
- DB_PASSWORD=bla
- DB_NAME=./test.sqlite
- ```
+1. Rename the .env.example file to .env (you can adjust app port and other settings, if needed)
2. Install Dependencies
```bash
yarn
@@ -25,15 +23,21 @@ Backend Server
yarn dev
```
-### Generate Docs
-```
-yarn docs
-```
-
-### Docker w/ postgres 🐳
-
+### Run Tests
```bash
-docker-compose up --build
+# Run tests once (server has to run)
+yarn test
+
+# Run test in watch mode (reruns on change)
+yarn test:watch
+
+# Run test in ci mode (automaticly starts the dev server)
+yarn test:ci
+```
+
+### Generate Docs
+```bash
+yarn docs
```
## Recommended Editor
@@ -42,22 +46,19 @@ docker-compose up --build
### Recommended Extensions
-- will be automatically recommended via ./vscode/extensions.json
+* will be automatically recommended via ./vscode/extensions.json
-## Branches
-- main: Protected "release" branch
-- dev: Current dev branch for merging the different features - only push for merges or minor changes!
-- feature/xyz: Feature branches - `feature/issueid-title`
-- bugfix/xyz: Branches for bugfixes - `bugfix/issueid-title` (no id for readme changes needed)
-
-
-## File Structure
-
-- src/models/entities\* - database models (typeorm entities)
-- src/models/actions\* - actions models
-- src/models/responses\* - response models
-- src/controllers/\* - routing-controllers
-- src/loaders/\* - loaders for the different init steps of the api server
-- src/middlewares/\* - express middlewares (mainly auth r/n)
-- src/errors/* - our custom (http) errors
-- src/routes/\* - express routes for everything we don't do via routing-controllers (depreciated)
\ No newline at end of file
+## Staging
+### Branches & Tags
+* vX.Y.Z: Release tags created from the main branch
+ * The version numbers follow the semver standard
+ * A new release tag automaticly triggers the release ci pipeline
+* main: Protected "release" branch
+ * The latest tag of the docker image get's build from this
+ * New releases get created as tags from this
+* dev: Current dev branch for merging the different feature branches and bugfixes
+ * The dev tag of the docker image get's build from this
+ * Only push minor changes to this branch!
+ * To merge a feature branch into this please create a pull request
+* feature/xyz: Feature branches - nameing scheme: `feature/issueid-title`
+* bugfix/xyz: Branches for bugfixes - nameing scheme:`bugfix/issueid-title`
\ No newline at end of file
diff --git a/package.json b/package.json
index 718d8d0..ade8a84 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-backend",
- "version": "0.2.0",
+ "version": "0.2.1",
"main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend",
"author": {
diff --git a/src/config.ts b/src/config.ts
index 580c90d..3bdcd27 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -9,7 +9,8 @@ export const config = {
jwt_secret: process.env.JWT_SECRET || "secretjwtsecret",
phone_validation_countrycode: getPhoneCodeLocale(),
postalcode_validation_countrycode: getPostalCodeLocale(),
- version: process.env.VERSION || require('../package.json').version
+ version: process.env.VERSION || require('../package.json').version,
+ seedTestData: getDataSeeding()
}
let errors = 0
if (typeof config.internal_port !== "number") {
@@ -30,4 +31,11 @@ function getPostalCodeLocale(): any {
return null;
}
}
+function getDataSeeding(): Boolean {
+ try {
+ return JSON.parse(process.env.SEED_TEST_DATA);
+ } catch (error) {
+ return false;
+ }
+}
export let e = errors
\ No newline at end of file
diff --git a/src/controllers/RunnerController.ts b/src/controllers/RunnerController.ts
index 389697b..fbaa381 100644
--- a/src/controllers/RunnerController.ts
+++ b/src/controllers/RunnerController.ts
@@ -8,6 +8,8 @@ import { UpdateRunner } from '../models/actions/update/UpdateRunner';
import { Runner } from '../models/entities/Runner';
import { ResponseEmpty } from '../models/responses/ResponseEmpty';
import { ResponseRunner } from '../models/responses/ResponseRunner';
+import { ResponseScan } from '../models/responses/ResponseScan';
+import { ResponseTrackScan } from '../models/responses/ResponseTrackScan';
import { DonationController } from './DonationController';
import { RunnerCardController } from './RunnerCardController';
import { ScanController } from './ScanController';
@@ -49,6 +51,31 @@ export class RunnerController {
return new ResponseRunner(runner);
}
+ @Get('/:id/scans')
+ @Authorized(["RUNNER:GET", "SCAN:GET"])
+ @ResponseSchema(ResponseScan, { isArray: true })
+ @ResponseSchema(ResponseTrackScan, { isArray: true })
+ @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
+ @OpenAPI({ description: 'Lists all scans of the runner whose id got provided.
If you only want the valid scans just add the ?onlyValid=true query param.' })
+ async getScans(@Param('id') id: number, onlyValid?: boolean) {
+ let responseScans: ResponseScan[] = new Array();
+ let runner = await this.runnerRepository.findOne({ id: id }, { relations: ['scans', 'scans.track', 'scans.station', 'scans.runner'] })
+ if (!runner) { throw new RunnerNotFoundError(); }
+
+ if (!onlyValid) {
+ for (let scan of runner.scans) {
+ responseScans.push(scan.toResponse());
+ }
+ }
+ else {
+ for (let scan of runner.validScans) {
+ responseScans.push(scan.toResponse());
+ }
+ }
+
+ return responseScans;
+ }
+
@Post()
@Authorized("RUNNER:CREATE")
@ResponseSchema(ResponseRunner)
diff --git a/src/controllers/RunnerSelfServiceController.ts b/src/controllers/RunnerSelfServiceController.ts
new file mode 100644
index 0000000..b2d2539
--- /dev/null
+++ b/src/controllers/RunnerSelfServiceController.ts
@@ -0,0 +1,49 @@
+import * as jwt from "jsonwebtoken";
+import { Get, JsonController, OnUndefined, Param } from 'routing-controllers';
+import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
+import { getConnectionManager, Repository } from 'typeorm';
+import { config } from '../config';
+import { InvalidCredentialsError } from '../errors/AuthError';
+import { RunnerNotFoundError } from '../errors/RunnerErrors';
+import { Runner } from '../models/entities/Runner';
+import { ResponseSelfServiceRunner } from '../models/responses/ResponseSelfServiceRunner';
+
+
+@JsonController('/runners')
+export class RunnerSelfServiceController {
+ private runnerRepository: Repository;
+
+ /**
+ * Gets the repository of this controller's model/entity.
+ */
+ constructor() {
+ this.runnerRepository = getConnectionManager().get().getRepository(Runner);
+ }
+
+ @Get('/me/:jwt')
+ @ResponseSchema(ResponseSelfServiceRunner)
+ @ResponseSchema(RunnerNotFoundError, { statusCode: 404 })
+ @OnUndefined(RunnerNotFoundError)
+ @OpenAPI({ description: 'Lists all information about yourself.
Please provide your runner jwt(that code we gave you during registration) for auth.
If you lost your jwt/personalized link please contact support.' })
+ async get(@Param('jwt') token: string) {
+ return (new ResponseSelfServiceRunner(await this.getRunner(token)));
+ }
+
+ /**
+ * Get's a runner by a provided jwt token.
+ * @param token The runner jwt provided by the runner to identitfy themselves.
+ */
+ private async getRunner(token: string): Promise {
+ let jwtPayload = undefined
+ try {
+ jwtPayload = jwt.verify(token, config.jwt_secret);
+ } catch (error) {
+ throw new InvalidCredentialsError();
+ }
+
+ const runner = await this.runnerRepository.findOne({ id: jwtPayload["id"] }, { relations: ['scans', 'group', 'scans.track', 'cards', 'distanceDonations', 'distanceDonations.donor', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] });
+ if (!runner) { throw new RunnerNotFoundError() }
+ return runner;
+ }
+
+}
\ No newline at end of file
diff --git a/src/loaders/database.ts b/src/loaders/database.ts
index 9dda78d..0b4a16a 100644
--- a/src/loaders/database.ts
+++ b/src/loaders/database.ts
@@ -1,6 +1,9 @@
import { createConnection } from "typeorm";
import { runSeeder } from 'typeorm-seeding';
-import { User } from '../models/entities/User';
+import { config } from '../config';
+import { ConfigFlag } from '../models/entities/ConfigFlags';
+import SeedPublicOrg from '../seeds/SeedPublicOrg';
+import SeedTestRunners from '../seeds/SeedTestRunners';
import SeedUsers from '../seeds/SeedUsers';
/**
* Loader for the database that creates the database connection and initializes the database tabels.
@@ -9,8 +12,20 @@ import SeedUsers from '../seeds/SeedUsers';
export default async () => {
const connection = await createConnection();
await connection.synchronize();
- if (await connection.getRepository(User).count() === 0) {
+
+ //The data seeding part
+ if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:user", value: "true" }))) {
await runSeeder(SeedUsers);
+ await connection.getRepository(ConfigFlag).save({ option: "seeded:user", value: "true" });
}
+ if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:citizenorg", value: "true" }))) {
+ await runSeeder(SeedPublicOrg);
+ await connection.getRepository(ConfigFlag).save({ option: "seeded:citizenorg", value: "true" });
+ }
+ if (!(await connection.getRepository(ConfigFlag).findOne({ option: "seeded:testdata", value: "true" })) && config.seedTestData == true) {
+ await runSeeder(SeedTestRunners);
+ await connection.getRepository(ConfigFlag).save({ option: "seeded:testdata", value: "true" });
+ }
+
return connection;
};
\ No newline at end of file
diff --git a/src/models/entities/ConfigFlags.ts b/src/models/entities/ConfigFlags.ts
new file mode 100644
index 0000000..3dd6c69
--- /dev/null
+++ b/src/models/entities/ConfigFlags.ts
@@ -0,0 +1,27 @@
+import {
+ IsNotEmpty,
+ IsString
+} from "class-validator";
+import { Column, Entity, PrimaryColumn } from "typeorm";
+
+/**
+ * Defines the ConfigFlag entity.
+ * This entity can be used to set some flags on db init.
+*/
+@Entity()
+export class ConfigFlag {
+ /**
+ * The flag's name (primary).
+ */
+ @PrimaryColumn()
+ @IsString()
+ option: string;
+
+ /**
+ * The flag's value.
+ */
+ @Column()
+ @IsString()
+ @IsNotEmpty()
+ value: string;
+}
diff --git a/src/models/entities/Runner.ts b/src/models/entities/Runner.ts
index bc6bc5a..266abb9 100644
--- a/src/models/entities/Runner.ts
+++ b/src/models/entities/Runner.ts
@@ -65,7 +65,7 @@ export class Runner extends Participant {
*/
@IsInt()
public get distanceDonationAmount(): number {
- return this.distanceDonations.reduce((sum, current) => sum + current.amountPerDistance, 0) * this.distance;
+ return this.distanceDonations.reduce((sum, current) => sum + current.amount, 0);
}
/**
diff --git a/src/models/responses/ResponseSelfServiceDonation.ts b/src/models/responses/ResponseSelfServiceDonation.ts
new file mode 100644
index 0000000..4f10f3c
--- /dev/null
+++ b/src/models/responses/ResponseSelfServiceDonation.ts
@@ -0,0 +1,36 @@
+import { IsInt, IsNotEmpty, IsPositive } from 'class-validator';
+import { DistanceDonation } from '../entities/DistanceDonation';
+
+/**
+ * Defines the runner selfservice donation response.
+ * Why? B/C runner's are not allowed to view all information available to admin users.
+*/
+export class ResponseSelfServiceDonation {
+ /**
+ * The donation's donor.
+ */
+ @IsNotEmpty()
+ donor: string;
+
+ /**
+ * The donation's amount in the smalles unit of your currency (default: euro cent).
+ */
+ @IsInt()
+ amount: number;
+
+ /**
+ * The donation's amount donated per distance.
+ * The amount the donor set to be donated per kilometer that the runner ran.
+ */
+ @IsInt()
+ @IsPositive()
+ amountPerDistance: number;
+
+ public constructor(donation: DistanceDonation) {
+ if (!donation.donor.middlename) { this.donor = donation.donor.firstname + " " + donation.donor.lastname; }
+ else { this.donor = donation.donor.firstname + " " + donation.donor.middlename + " " + donation.donor.lastname; }
+
+ this.amountPerDistance = donation.amountPerDistance;
+ this.amount = donation.amount;
+ }
+}
\ No newline at end of file
diff --git a/src/models/responses/ResponseSelfServiceRunner.ts b/src/models/responses/ResponseSelfServiceRunner.ts
new file mode 100644
index 0000000..6727c76
--- /dev/null
+++ b/src/models/responses/ResponseSelfServiceRunner.ts
@@ -0,0 +1,75 @@
+import { IsInt, IsString } from "class-validator";
+import { DistanceDonation } from '../entities/DistanceDonation';
+import { Runner } from '../entities/Runner';
+import { RunnerGroup } from '../entities/RunnerGroup';
+import { RunnerTeam } from '../entities/RunnerTeam';
+import { ResponseParticipant } from './ResponseParticipant';
+import { ResponseSelfServiceDonation } from './ResponseSelfServiceDonation';
+
+/**
+ * Defines the runner selfservice response.
+ * Why? B/C runner's are not allowed to view all information available to admin users.
+*/
+export class ResponseSelfServiceRunner extends ResponseParticipant {
+
+ /**
+ * The runner's currently ran distance in meters.
+ */
+ @IsInt()
+ distance: number;
+
+ /**
+ * The runner's currently collected donations.
+ */
+ @IsInt()
+ donationAmount: number;
+
+ /**
+ * The runner's group as a string (mix of org and team).
+ */
+ @IsString()
+ group: string;
+
+ /**
+ * The runner's associated donations.
+ */
+ @IsString()
+ donations: ResponseSelfServiceDonation[]
+
+ /**
+ * 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.distance;
+ this.donationAmount = runner.distanceDonationAmount;
+ this.group = this.getTeamString(runner.group);
+ this.donations = this.getDonations(runner.distanceDonations);
+ }
+
+ /**
+ * Parses a runner's group into a string.
+ * If the runner's group is a team: `org name/team name`
+ * If the runner's group is an org: `org name`
+ * @param group The group that shall get parsed to a string.
+ */
+ private getTeamString(group: RunnerGroup): string {
+ if (group instanceof RunnerTeam) {
+ return group.parentGroup.name + "/" + group.name;
+ }
+ return group.name;
+ }
+
+ /**
+ * Converts all of the runner's donations to ResponseSelfServiceDonations.
+ * @param donations The donations that shall be converted to ResponseSelfServiceDonations.
+ */
+ private getDonations(donations: DistanceDonation[]): ResponseSelfServiceDonation[] {
+ let responseDonations = new Array();
+ for (let donation of donations) {
+ responseDonations.push(new ResponseSelfServiceDonation(donation));
+ }
+ return responseDonations;
+ }
+}
diff --git a/src/seeds/SeedPublicOrg.ts b/src/seeds/SeedPublicOrg.ts
new file mode 100644
index 0000000..57685f8
--- /dev/null
+++ b/src/seeds/SeedPublicOrg.ts
@@ -0,0 +1,15 @@
+import { Connection } from 'typeorm';
+import { Factory, Seeder } from 'typeorm-seeding';
+import { CreateRunnerOrganisation } from '../models/actions/create/CreateRunnerOrganisation';
+import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
+
+/**
+ * Seeds the public runner org (named: "Citizen" by default).
+ */
+export default class SeedPublicOrg implements Seeder {
+ public async run(factory: Factory, connection: Connection): Promise {
+ let publicOrg = new CreateRunnerOrganisation();
+ publicOrg.name = "Citizen";
+ await connection.getRepository(RunnerOrganisation).save(await publicOrg.toEntity());
+ }
+}
\ No newline at end of file
diff --git a/src/seeds/SeedTestRunners.ts b/src/seeds/SeedTestRunners.ts
new file mode 100644
index 0000000..14725c6
--- /dev/null
+++ b/src/seeds/SeedTestRunners.ts
@@ -0,0 +1,93 @@
+import { Connection } from 'typeorm';
+import { Factory, Seeder } from 'typeorm-seeding';
+import { CreateGroupContact } from '../models/actions/create/CreateGroupContact';
+import { CreateRunner } from '../models/actions/create/CreateRunner';
+import { CreateRunnerOrganisation } from '../models/actions/create/CreateRunnerOrganisation';
+import { CreateRunnerTeam } from '../models/actions/create/CreateRunnerTeam';
+import { Address } from '../models/entities/Address';
+import { GroupContact } from '../models/entities/GroupContact';
+import { Runner } from '../models/entities/Runner';
+import { RunnerGroup } from '../models/entities/RunnerGroup';
+import { RunnerOrganisation } from '../models/entities/RunnerOrganisation';
+import { RunnerTeam } from '../models/entities/RunnerTeam';
+
+/**
+ * Seeds a test runner org with a test runner team ans some test runners.
+ * Usefull for testing or demo instances.
+ */
+export default class SeedTestRunners implements Seeder {
+ public async run(factory: Factory, connection: Connection): Promise {
+ let testOrg: RunnerOrganisation = await this.createTestOrg(connection);
+ let testTeam: RunnerTeam = await this.createTestTeam(connection, testOrg);
+ await this.createTestContact(connection, testOrg);
+ await this.createTestRunners(connection, testOrg);
+ await this.createTestRunners(connection, testTeam);
+ }
+
+ public async createTestOrg(connection: Connection): Promise {
+ let testOrg = new CreateRunnerOrganisation();
+ testOrg.name = "Test Org";
+
+ testOrg.address = new Address();
+ testOrg.address.address1 = "Test street 1";
+ testOrg.address.city = "Herzogenaurach";
+ testOrg.address.country = "Germany";
+ testOrg.address.postalcode = "90174";
+
+ return await connection.getRepository(RunnerOrganisation).save(await testOrg.toEntity());
+ }
+
+ public async createTestTeam(connection: Connection, org: RunnerOrganisation): Promise {
+ let testTeam = new CreateRunnerTeam();
+ testTeam.name = "Test Team";
+ testTeam.parentGroup = org.id;
+
+ return await connection.getRepository(RunnerTeam).save(await testTeam.toEntity());
+ }
+
+ public async createTestRunners(connection: Connection, group: RunnerGroup) {
+ for (let first of this.firstnames) {
+ for (let last of this.lastnames) {
+ let runner = new CreateRunner;
+ runner.firstname = first;
+ runner.lastname = last;
+ runner.middlename = group.name;
+ runner.group = group.id;
+ await connection.getRepository(Runner).save(await runner.toEntity());
+ }
+ }
+ }
+
+ public async createTestContact(connection: Connection, group: RunnerGroup) {
+ let contact = new CreateGroupContact;
+ contact.firstname = "Test";
+ contact.lastname = "Contact";
+ contact.email = "test.contact@dev.lauf-fuer-kaya.de";
+ contact.groups = group.id;
+
+ contact.address = new Address();
+ contact.address.address1 = "First Contact Street 100";
+ contact.address.city = "Herzogenaurach";
+ contact.address.country = "Germany";
+ contact.address.postalcode = "90174";
+
+ await connection.getRepository(GroupContact).save(await contact.toEntity());
+ }
+
+ private firstnames = [
+ "Peter",
+ "Matze",
+ "Tine",
+ "Uta",
+ "Fabian",
+ "Unicode:ÖÄ?✔⚠"
+ ]
+
+ private lastnames = [
+ "Muster",
+ "Example",
+ "Müller",
+ "Unicode:搆Ǩ>ÙՠƳ|"
+ ]
+
+}
\ No newline at end of file
diff --git a/src/tests/scans/scans_get.spec.ts b/src/tests/scans/scans_get.spec.ts
index da7d737..a4128b7 100644
--- a/src/tests/scans/scans_get.spec.ts
+++ b/src/tests/scans/scans_get.spec.ts
@@ -61,9 +61,17 @@ describe('adding + getting scans', () => {
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json")
});
- it('check if scans was added (no parameter validation)', async () => {
+ it('check if scans was added directly', async () => {
const res = await axios.get(base + '/api/scans/' + added_scan.id, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
+ expect(res.data).toEqual(added_scan);
+ });
+ it('check if scans was added via the runner/scans endpoint.', async () => {
+ const res = await axios.get(base + '/api/runners/' + added_runner.id + "/scans", axios_config);
+ expect(res.status).toEqual(200);
+ expect(res.headers['content-type']).toContain("application/json");
+ added_scan.runner.distance = 0;
+ expect(res.data).toContainEqual(added_scan);
});
});
\ No newline at end of file