diff --git a/.eslintrc.js b/.eslintrc.js index 1c408a4..5ffde75 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,9 @@ +const SEVERITY = { + OFF: 0, + WARNING: 1, + ERROR: 2 +}; + module.exports = { parser: '@typescript-eslint/parser', // Specifies the ESLint parser extends: [ @@ -12,39 +18,31 @@ module.exports = { project: './tsconfig.json' }, rules: { - "comma-dangle": [2, "always-multiline"], - "quotes": [2, "double"], - "eqeqeq": [2, "always"], - "arrow-parens": [2, "as-needed"], - "space-before-function-paren": [2, { + "comma-dangle": [SEVERITY.ERROR, "always-multiline"], + "quotes": [SEVERITY.ERROR, "double"], + "eqeqeq": [SEVERITY.ERROR, "always"], + "arrow-parens": [SEVERITY.ERROR, "as-needed"], + "space-before-function-paren": [SEVERITY.ERROR, { "anonymous": "never", "named": "never", "asyncArrow": "always" }], - "object-curly-spacing": [2, "always"], - "max-len": ["warn", { "code": 120, "tabWidth": 2 }], - "@typescript-eslint/no-use-before-define": 2, - "@typescript-eslint/no-namespace": 0, - "@typescript-eslint/no-empty-interface": 0, - "no-unused-vars": 2, - "@typescript-eslint/no-unused-vars": 2, - "@typescript-eslint/no-explicit-any": 2, - "@typescript-eslint/explicit-function-return-type": 0, - "@typescript-eslint/no-var-requires": 2, - "require-jsdoc": [2, { - "require": { - "FunctionDeclaration": true, - "MethodDefinition": true, - "ClassDeclaration": true, - "ArrowFunctionExpression": true, - "FunctionExpression": true - } - }], - "unused-imports/no-unused-imports-ts": 2, - "unused-imports/no-unused-vars-ts": 1, - "@typescript-eslint/interface-name-prefix": 0, + "object-curly-spacing": [SEVERITY.ERROR, "always"], + "max-len": [SEVERITY.WARNING, { "code": 120, "tabWidth": 2 }], + "@typescript-eslint/no-use-before-define": SEVERITY.WARNING, + "@typescript-eslint/no-namespace": SEVERITY.OFF, + "@typescript-eslint/no-empty-interface": SEVERITY.OFF, + "no-unused-vars": SEVERITY.ERROR, + "@typescript-eslint/no-unused-vars": SEVERITY.ERROR, + "@typescript-eslint/no-explicit-any": SEVERITY.ERROR, + "@typescript-eslint/explicit-function-return-type": SEVERITY.OFF, + "@typescript-eslint/no-var-requires": SEVERITY.ERROR, + "require-jsdoc": SEVERITY.OFF, + "unused-imports/no-unused-imports-ts": SEVERITY.ERROR, + "unused-imports/no-unused-vars-ts": SEVERITY.WARNING, + "@typescript-eslint/interface-name-prefix": SEVERITY.OFF, "@typescript-eslint/naming-convention": [ - 2, + SEVERITY.ERROR, { "selector": "default", "format": ["PascalCase", "UPPER_CASE"] diff --git a/.travis.yml b/.travis.yml index 61623f7..496577b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,7 @@ env: - NODE_ENV=development stages: - - "Audit" + #- "Audit" - "Build" - "Test" - "Lint" @@ -30,12 +30,12 @@ stages: jobs: include: - - stage: "Audit" - if: type = pull_request - script: audit-ci --low --report-type full + #- stage: "Audit" + # if: type = pull_request + # script: audit-ci --low --report-type full - &build stage: "Build" - script: npm run-script build + script: npm run-script tsoa:gen && npm run-script build node_js: '8' - <<: *build node_js: '10' @@ -43,6 +43,7 @@ jobs: node_js: '12' - stage: "Test" script: + - npm run-script tsoa:gen - git fetch --unshallow --quiet - sudo mysql -u root < test/ci.sql - npm run-script test-coverage diff --git a/README.md b/README.md index dedb862..06a3e64 100644 --- a/README.md +++ b/README.md @@ -34,868 +34,23 @@ npm install npm run build ``` -4. Run the api +4. Create a `.env` file in the root of the folder containing the needed environment-variables. + +5. Run the api ``` npm start ``` -5. Or start the server in watch-mode, recompiling and restarting on changes +Or start the server in watch-mode, recompiling and restarting on changes ``` npm run watch ``` -# Routes - -### Authentication - -#### Login - -``` -/v1/auth/login -``` - -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| email | The email of the user | Yes | -| password | The password of the user | Yes | - -**Returns:** a JWT-Token for authentications. - -**Example:** -``` -{ - "token": "JWT-TOKEN" -} -``` - -### Configurations - -#### Get the current game-configuration - -``` -/v1/config/game -``` -**Request-Type:** GET - -**Parameters:** none - -**Returns:** The current game-config - -**Example:** - -``` -{ - "speed": 1, - "metalStart": 500, - "crystalStart": 500, - "deuteriumStart": 500, - "startPlanetName": "Homeplanet", - "startPlanetDiameter": 150000, - "startPlanetMaxFields": 138, - "posGalaxyMax": 9, - "posSystemMax": 100, - "posPlanetMax": 15 -} -``` - -#### Get the current unit-configuration - -``` -/v1/config/units -``` - -**Request-Type:** GET - -**Parameters:** none - -**Returns:** The config for all ingame-units - -**Example:** - -``` -{ - "units": { - "buildings": { ... }, - "ships": { ... }, - "defenses": { ... }, - "technologies": { ... } - } - "requirements": { ... }, - "mappings": { ... } -} -``` - -### Planets - -#### Get a specific planet -``` -/v1/planets/{planetID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-------------|-------------------------------|----------| -| planetID | The ID of the planet | Yes | - - -**Returns:** Available information about the planet - -**Example:** - -``` -{ - "planetID": 333, - "ownerID": 76487, - "name": "Homeplanet", - "posGalaxy": 1, - "posSystem": 16, - "posPlanet": 11, - "lastUpdate": 1521056629, - "planetType": 1, - "image": "trockenplanet08", - "destroyed": 0 -} -``` - -#### Get a specific planet owned by the current player -``` -/v1/user/planet/{planetID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-------------|-------------------------------|----------| -| planetID | The ID of the planet | Yes | - - -**Returns:** Available information about the planet - -**Example:** - -``` -{ - "planetID": 60881, - "ownerID": 1, - "name": "test123", - "posGalaxy": 1, - "posSystem": 4, - "posPlanet": 3, - "lastUpdate": 1521057636, - "planetType": 1, - "image": "trockenplanet08", - "diameter": 11188, - "fieldsCurrent": 0, - "fieldsMax": 125, - . - . - . -} -``` - -#### Get all planets of the current player -``` -/v1/user/planetlist -``` -**Request-Type:** GET - -**Parameters:** none - -**Returns:** A list of all planets owned by the current player - -**Example:** -``` -[ - { - "planetID": 60881, - "ownerID": 1, - "name": "Homeplanet", - . - . - . - }, - { - "planetID": 167546850, - "ownerID": 1, - "name": "Planet", - . - . - . - } -] -``` - -#### Set the current planet -``` -/v1/user/currentplanet/set -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-------------|----------------------------------|----------| -| planetID | The ID of the new current planet | Yes | - -**Returns:** Success or Error as HTTP status code - -#### Destroy a planet -``` -/v1/planet/destroy -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-------------|----------------------------------|----------| -| planetID | The ID of the new current planet | Yes | - -**Returns:** Success or Error as HTTP status code - -#### Rename a planet -``` -/v1/planet/rename -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-------------|----------------------------------|----------| -| planetID | The ID of the new current planet | Yes | -| name | The new name for the planet | Yes | - -**Returns:** Success or Error as HTTP status code - -### Users - -#### Get the current user -``` -/v1/user -``` -**Request-Type:** GET - -**Parameters:** none - -**Returns:** The current user - -**Example:** - -``` -{ - "userID": 1, - "username": "admin", - "email": "xxx@xxx.xx", - "lastTimeOnline": "1548524754", - "currentPlanet": 167546850 -} -``` - -#### Get a specific user - -``` -/v1/users/{userID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| userID | The ID of the user | Yes | - -**Returns:** The current user - -**Example:** -``` -{ - "userID": 1, - "username": "xxx" -} -``` - -#### Create a new user -``` -/v1/users/create -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-------------|-------------------------------|----------| -| username | The username for the new user | Yes | -| email | The email for the new user | Yes | -| password | The password for the new user | Yes | - -**Returns:** Success or Error as HTTP status code - -#### Update an user -``` -/v1/users/update -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-------------|-------------------------------|----------| -| username | The username for the new user | No | -| email | The email for the new user | No | -| password | The password for the new user | No | - -**Returns:** Success or Error as HTTP status code - -### Buildings - -#### Get all buildings on a planet -``` -/v1/buildings/{planetID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| planetID | The ID of the planet | Yes | - -**Returns:** A list of all buildings on the given planet. - -**Example:** - -``` -{ - "ownerID": 1, - "planetID": 60881, - "metalMine": 1, - "crystalMine": 1, - "deuteriumSynthesizer": 4, - "solarPlant": 1, - "fusionReactor": 0, - "roboticFactory": 3, - "naniteFactory": 0, - "shipyard": 8, - "metalStorage": 2, - "crystalStorage": 3, - "deuteriumStorage": 2, - "researchLab": 1, - "terraformer": 0, - "allianceDepot": 0, - "missileSilo": 0 -} -``` - -#### Build a specific buildings on a planet -``` -/v1/buildings/build -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| planetID | The ID of the planet | Yes | -| buildingID| The ID of the building | Yes | - - -**Returns:** The updated planet. - -**Example:** - -``` -{ - "planetID": 60881, - "ownerID": 1, - "name": "Sampleplanet", - "posGalaxy": 1, - "posSystem": 4, - "posPlanet": 3, - . - . - . -} -``` - -#### Cancel a build-order on a planet -``` -/v1/buildings/cancel -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| planetID | The ID of the planet | Yes | -| buildingID| The ID of the building | Yes | - - -**Returns:** The updated planet. - -**Example:** - -``` -{ - "planetID": 60881, - "ownerID": 1, - "name": "Sampleplanet", - "posGalaxy": 1, - "posSystem": 4, - "posPlanet": 3, - . - . - . -} -``` - -### Ships - -#### Get all ships on a planet -``` -/v1/ships/{planetID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| planetID | The ID of the planet | Yes | - -**Returns:** A list of all ships on the given planet. - -**Example:** - -``` -{ - "ownerID": 1, - "planetID": 60881, - "smallCargoShip": 0, - "largeCargoShip": 0, - "lightFighter": 0, - "heavyFighter": 0, - "cruiser": 0, - "battleship": 0, - "colonyShip": 0, - "recycler": 0, - "espionageProbe": 0, - "bomber": 0, - "solarSatellite": 0, - "destroyer": 0, - "battlecruiser": 0, - "deathstar": 0 -} -``` - -#### Build ships on a planet -``` -/v1/ships/build -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------------------------------------------|----------| -| planetID | The ID of the planet | Yes | -| buildOrder| A JSON-string with key-value pairs. {unitID: amount, ...} | Yes | - - -**Returns:** Success or Error as HTTP status code - -### Defenses - -#### Get all defenses on a planet -``` -/v1/defenses/{planetID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| planetID | The ID of the planet | Yes | - -**Returns:** A list of all defenses on the given planet. - -**Example:** - -``` -{ - "ownerID": 1, - "planetID": 60881, - "rocketLauncher": 0, - "lightLaser": 0, - "heavyLaser": 0, - "ionCannon": 0, - "gaussCannon": 0, - "plasmaTurret": 0, - "smallShieldDome": 0, - "largeShieldDome": 0, - "antiBallisticMissile": 0, - "interplanetaryMissile": 0 -} -``` - -#### Build defenses on a planet -``` -/v1/defenses/build -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------------------------------------------|----------| -| planetID | The ID of the planet | Yes | -| buildOrder| A JSON-string with key-value pairs. {unitID: amount, ...} | Yes | - - -**Returns:** Success or Error as HTTP status code - -### Technologies - -#### Get all technologies -``` -/v1/techs -``` -**Request-Type:** GET - -**Parameters:** none - -**Returns:** A list of all technologies the player has. - -**Example:** - -``` -{ - "userID": 1, - "espionageTech": 0, - "computerTech": 0, - "weaponTech": 0, - "armourTech": 0, - "shieldingTech": 0, - "energyTech": 0, - "hyperspaceTech": 0, - "combustionDriveTech": 0, - "impulseDriveTech": 0, - "hyperspaceDriveTech": 0, - "laserTech": 0, - "ionTech": 0, - "plasmaTech": 0, - "intergalacticResearchTech": 0, - "gravitonTech": 0 -} -``` - -#### Build a technology -``` -/v1/techs/build -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| planetID | The ID of the planet | Yes | -| techID | The ID of the technology | Yes | - -**Returns:** Success or Error as HTTP status code - -#### Cancel a technology -``` -/v1/techs/cancel -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| planetID | The ID of the planet | Yes | - - -**Returns:** Success or Error as HTTP status code - -### Galaxy +# API specification -#### Get information for a given galaxy and system -``` -/v1/galaxy/{posGalaxy}/{posSystem} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| posGalaxy | The galaxy-position | Yes | -| posSystem | The system-position | Yes | - -**Returns:** Information about the galaxy - -**Example:** - -``` -[ - { - "planetID": 1476777762, - "ownerID": 751782555, - "username": "Testuser", - "name": "Homeplanet", - "posGalaxy": 4, - "posSystem": 88, - "posPlanet": 6, - "lastUpdate": 1558452853, - "planetType": 1, - "image": "normal3.png", - "debrisMetal": 0, - "debrisCrystal": 0, - "destroyed": 0 - }, - { - . - . - . - }, - . - . - . -] -``` - -### Messages - -#### Get all messages -``` -/v1/messages/get -``` -**Request-Type:** GET - -**Parameters:** none - -**Returns:** A list of all messages sent to the current user - -**Example:** - -``` -[ - { - "messageID": 6, - "senderID": 1, - "receiverID": 1, - "sendtime": 1558030571, - "type": 1, - "subject": "test1", - "body": "test1" - }, - { - "messageID": 5, - "senderID": 1, - "receiverID": 1, - "sendtime": 1558030570, - "type": 1, - "subject": "test2", - "body": "test2" - } -] -``` - -#### Get a specific message -``` -/v1/messages/get/{messageID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| messageID | The ID of the message | Yes | - -**Returns:** The message - -**Example:** - -``` -{ - "messageID": 5, - "senderID": 1, - "receiverID": 1, - "sendtime": 1558030570, - "type": 1, - "subject": "test", - "body": "test" -} -``` - -#### Send a message -``` -/v1/messages/send -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|------------|----------------------------|----------| -| receiverID | The ID of the receiver | Yes | -| subject | The subject of the message | Yes | -| body | The body of the message | Yes | - -**Returns:** Success or Error as HTTP status code - -#### Delete a message -``` -/v1/messages/delete -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|-----------|--------------------------|----------| -| messageID | The ID of the message | Yes | - -**Returns:** Success or Error as HTTP status code - -### Events - -#### Send fleet -``` -/v1/events/create/ -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|------------|---------------------------------|----------| -| event | The event-data as a JSON object | Yes | - -**Schema:** https://api.ugamela.org/fleetevent.schema.json - -**Returns:** The data of the created event - -**Example:** - -``` -{ - "eventID": 15, - "ownerID": 1, - "mission": "attack", - "speed": 30, - "data": { - "origin": { - "posGalaxy": 1, - "posSystem": 4, - "posPlanet": 3, - "type": "planet" - }, - "destination": { - "posGalaxy": 9, - "posSystem": 84, - "posPlanet": 14, - "type": "planet" - }, - "ships": { - "201": 612, - "202": 357, - "203": 617, - "204": 800, - "205": 709, - "206": 204, - "207": 703, - "208": 85, - "209": 631, - "210": 388, - "211": 0, - "212": 723, - "213": 557, - "214": 106 - }, - "loadedRessources": { - "metal": 443, - "crystal": 980, - "deuterium": 220 - } - }, - "starttime": 1558456480, - "endtime": 1558470153 -} -``` - -#### Call back fleet -``` -/v1/events/cancel/ -``` -**Request-Type:** POST - -**Parameters:** - -| Parameter | Description | Required | -|------------|---------------------------------|----------| -| eventID | The ID of the event | Yes | - -**Returns:** Success or Error as HTTP status code - -#### Get all events on a planet -``` -/v1/planets/movement/{planetID} -``` -**Request-Type:** GET - -**Parameters:** - -| Parameter | Description | Required | -|------------|----------------------------|----------| -| planetID | The ID of the planet | Yes | - -**Returns:** A list of events happening on the given planet - -**Example:** - -``` -[ - { - "eventID": 1, - "ownerID": 1, - "mission": 2, - "fleetlist": "{\"201\":612,\"202\":357,\"203\":617,\"204\":800,\"205\":709,\"206\":204,\"207\":703,\"208\":85,\"209\":631,\"210\":388,\"211\":0,\"212\":723,\"213\":557,\"214\":106}", - "startID": 60881, - "startType": 1, - "startTime": 1558449681, - "endID": 18341, - "endType": 1, - "endTime": 1558450681, - "loadedMetal": 443, - "loadedCrystal": 980, - "loadedDeuterium": 220, - "returning": 0 - }, - { - "eventID": 1, - "ownerID": 1, - . - . - . - } -] -``` +The OpenAPI specification can be found by navigating to the swagger-UI (`/v1/swagger`). The specification is defined in `/src/tsoa/swagger.json`. # Support / Questions diff --git a/gulpfile.js b/gulpfile.js index 7db415c..845d424 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,14 +11,7 @@ const SOURCE_FILES = ["src/**/*.ts", "!" + TEST_FILES]; const tsProject = ts.createProject("tsconfig.json"); gulp.task("compile", () => { - const tsResult = gulp.src(SOURCE_FILES).pipe( - tsProject("./tsconfig.json", { - logLevel: 1, - compilerOptions: { - listFiles: true, - }, - }), - ); + const tsResult = gulp.src(SOURCE_FILES).pipe(tsProject()); return tsResult.js.pipe(gulp.dest("dist")); }); diff --git a/package-lock.json b/package-lock.json index d03f1a1..1a9c31c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -127,12 +127,6 @@ "to-fast-properties": "^2.0.0" } }, - "@fluffy-spoon/substitute": { - "version": "1.120.0", - "resolved": "https://registry.npmjs.org/@fluffy-spoon/substitute/-/substitute-1.120.0.tgz", - "integrity": "sha512-zCPUcT2TLILGgL32LgQ/zTE+FQgCZRi5Asle/5r2GiF6TRuGUHBPYdcp+fJiEYRD6pCAQNl/n2c4BWunRmotuQ==", - "dev": true - }, "@istanbuljs/nyc-config-typescript": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/nyc-config-typescript/-/nyc-config-typescript-0.1.3.tgz", @@ -693,7 +687,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" }, @@ -701,8 +694,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" } } }, @@ -747,17 +739,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" - } - }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -816,16 +797,6 @@ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1342,12 +1313,6 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -1732,8 +1697,12 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "commandpost": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.4.0.tgz", + "integrity": "sha512-aE2Y4MTFJ870NuB/+2z1cXBhSBBzRydVVjzhFC4gtenEhpnj15yu0qptWGJsO9YGrcPZ3ezX8AWb1VA391MKpQ==" }, "commondir": { "version": "1.0.1", @@ -1780,12 +1749,6 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -1905,6 +1868,11 @@ "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" }, + "csp-header": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/csp-header/-/csp-header-2.1.1.tgz", + "integrity": "sha512-JuUi95nVFMuVOcI/A10+8CwpWflq3TS/sgperD109BG0xg7pEY1x1E1Aqv0LIpIKHdgkCK0nIZqu1RVRRRoMJA==" + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -1973,6 +1941,11 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + }, "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -2187,6 +2160,33 @@ "safe-buffer": "^5.0.1" } }, + "editorconfig": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", + "integrity": "sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g==", + "requires": { + "commander": "^2.19.0", + "lru-cache": "^4.1.5", + "semver": "^5.6.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2432,262 +2432,6 @@ "get-stdin": "^6.0.0" } }, - "eslint-import-resolver-node": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", - "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "eslint-module-utils": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", - "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.20.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", - "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", - "dev": true, - "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.1", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.0", - "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, "eslint-plugin-prettier": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz", @@ -3323,6 +3067,15 @@ } } }, + "express-csp-header": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/express-csp-header/-/express-csp-header-4.0.0.tgz", + "integrity": "sha512-ksDlHZUHmZtNyULAuFqeX91q1bVNJk8z26cYRrKmPxkgeqEDrdI72uvI77/8ufxsnPLZ9gbgmt31fWVXD4d0pw==", + "requires": { + "csp-header": "^2.1.1", + "psl": "^1.8.0" + } + }, "express-ip": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/express-ip/-/express-ip-1.0.4.tgz", @@ -3801,7 +3554,6 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -4657,7 +4409,6 @@ "version": "4.7.6", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "dev": true, "requires": { "minimist": "^1.2.5", "neo-async": "^2.6.0", @@ -4669,8 +4420,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -4866,6 +4616,11 @@ } } }, + "http-status-codes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-1.4.0.tgz", + "integrity": "sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ==" + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -5053,6 +4808,241 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" }, + "inversify": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", + "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==" + }, + "inversify-binding-decorators": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/inversify-binding-decorators/-/inversify-binding-decorators-4.0.0.tgz", + "integrity": "sha512-r8au/oH3vS7ttHj0RivAznwElySeRohLfg8lvOSzbrX6abf/8ik8ptk49XbzdShgrnalvl7CM6MjcskfM7MMqQ==" + }, + "inversify-express-utils": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/inversify-express-utils/-/inversify-express-utils-6.3.2.tgz", + "integrity": "sha512-zIFMJVPTcXzxZBmwtWV2b26MGwPzbZ/XM5SPnrE0SqNQ3QqgI8LMV3nbCLhaPrSji5KToo77UXK5yFpPdVgWJQ==", + "requires": { + "express": "4.16.2", + "http-status-codes": "^1.3.0" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "express": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", + "requires": { + "accepts": "~1.3.4", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.1", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.0", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.2", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.1", + "serve-static": "1.13.1", + "setprototypeof": "1.1.0", + "statuses": "~1.3.1", + "type-is": "~1.6.15", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "dependencies": { + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + } + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + }, + "send": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.1", + "destroy": "~1.0.4", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.3.1" + } + }, + "serve-static": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", + "requires": { + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.1" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + } + } + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -5302,12 +5292,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, "is-symbol": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", @@ -5555,7 +5539,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "dev": true, "requires": { "graceful-fs": "^4.1.6" } @@ -5720,7 +5703,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -5729,8 +5711,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" } } }, @@ -5935,6 +5916,11 @@ "p-is-promise": "^2.0.0" } }, + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==" + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -6378,6 +6364,11 @@ "integrity": "sha1-Rpve9PivyaEWBW8HnfYYLQr7A4Q=", "dev": true }, + "moment": { + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", + "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" + }, "morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -6523,8 +6514,7 @@ "neo-async": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==" }, "nested-error-stacks": { "version": "2.1.0", @@ -7058,18 +7048,6 @@ "make-iterator": "^1.0.0" } }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - } - }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -7181,7 +7159,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -7190,7 +7167,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -7198,8 +7174,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-hash": { "version": "3.0.0", @@ -7463,6 +7438,11 @@ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, "pstree.remy": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz", @@ -7615,6 +7595,11 @@ "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.2.0.tgz", "integrity": "sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==" }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -7979,6 +7964,11 @@ "rechoir": "^0.6.2" } }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -8416,6 +8406,19 @@ "es6-symbol": "^3.1.1" } }, + "swagger-ui-dist": { + "version": "3.28.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.28.0.tgz", + "integrity": "sha512-aPkfTzPv9djSiZI1NUkWr5HynCUsH+jaJ0WSx+/t19wq7MMGg9clHm9nGoIpAtqml1G51ofI+I75Ym72pukzFg==" + }, + "swagger-ui-express": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.4.tgz", + "integrity": "sha512-Ea96ecpC+Iq9GUqkeD/LFR32xSs8gYqmTW1gXCuKg81c26WV6ZC2FsBSPVExQP6WkyUuz5HEiR0sEv/HCC343g==", + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -8721,6 +8724,15 @@ "resolved": "https://registry.npmjs.org/ts-graylog/-/ts-graylog-1.0.2.tgz", "integrity": "sha512-jTr0rEjxp2NS/5M6eZJSWHC2P/igicOdpFmMe30JqgnoSgqY4GdZferzv/k97ldjU63kaAyR9wCNGciNtopXLQ==" }, + "ts-mockito": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", + "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", + "dev": true, + "requires": { + "lodash": "^4.17.5" + } + }, "ts-node": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz", @@ -8748,40 +8760,136 @@ "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", "dev": true }, - "tslint": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.1.tgz", - "integrity": "sha512-kd6AQ/IgPRpLn6g5TozqzPdGNZ0q0jtXW4//hRcj10qLYBaa3mTUU2y2MCG+RXZm8Zx+KZi0eA+YCrMyNlF4UA==", - "dev": true, + "tsoa": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/tsoa/-/tsoa-3.2.1.tgz", + "integrity": "sha512-Liq5T84wzMw0wZ9pVlDHGN15m3E95caj1zzz7ZsTnFm6wrtO7pVDWLZFJ88nV+zRywPUsO9yzrwybhH86FQS9g==", "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", + "deepmerge": "^4.2.2", + "fs-extra": "^8.1.0", + "glob": "^7.1.6", + "handlebars": "^4.7.6", + "merge": "^1.2.1", "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.10.0", - "tsutils": "^2.29.0" + "moment": "^2.24.0", + "typescript": "^3.9.2", + "typescript-formatter": "^7.2.2", + "validator": "^12.2.0", + "yamljs": "^0.3.0", + "yargs": "^14.0.0" }, "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "typescript": { + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz", + "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==" + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yargs": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", + "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.1" + } + }, + "yargs-parser": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", + "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", "requires": { - "tslib": "^1.8.1" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -8887,11 +8995,19 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "typescript-formatter": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-7.2.2.tgz", + "integrity": "sha512-V7vfI9XArVhriOTYHPzMU2WUnm5IMdu9X/CPxs8mIMGxmTBFpDABlbkBka64PZJ9/xgQeRpK8KzzAG4MPzxBDQ==", + "requires": { + "commandpost": "^1.0.0", + "editorconfig": "^0.15.0" + } + }, "uglify-js": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.0.tgz", "integrity": "sha512-j5wNQBWaql8gr06dOUrfaohHlscboQZ9B8sNsoK5o4sBjm7Ht9dxSbrMXyktQpA16Acaij8AcoozteaPYZON0g==", - "dev": true, "optional": true, "requires": { "commander": "~2.20.3" @@ -8983,8 +9099,7 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unpipe": { "version": "1.0.0", @@ -9164,6 +9279,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "validator": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-12.2.0.tgz", + "integrity": "sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==" + }, "value-or-function": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", @@ -9335,8 +9455,7 @@ "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=" }, "wrap-ansi": { "version": "2.1.0", @@ -9397,6 +9516,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, + "yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "requires": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + } + }, "yargs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", diff --git a/package.json b/package.json index f1b6b38..7b12a5e 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "lint-fix": "eslint src/**/*.ts --fix", "tsc": "tsc --noEmit --project .", "build": "gulp build", - "watch": "gulp" + "watch": "gulp", + "tsoa:gen": "tsoa routes && tsoa swagger" }, "nyc": { "branches": 0, @@ -66,7 +67,6 @@ "excludeNodeModules": true }, "devDependencies": { - "@fluffy-spoon/substitute": "^1.89.0", "@istanbuljs/nyc-config-typescript": "^0.1.3", "@types/bcryptjs": "^2.4.2", "@types/body-parser": "0.0.33", @@ -97,6 +97,7 @@ "nyc": "^14.1.1", "prettier": "^1.18.2", "source-map-support": "^0.5.12", + "ts-mockito": "^2.6.1", "ts-node": "^8.2.0", "typedoc": "^0.15.6", "typescript": "^3.7.3" @@ -111,6 +112,7 @@ "dotenv": "^8.2.0", "express": "^4.16.4", "express-ip": "^1.0.3", + "express-csp-header": "^4.0.0", "express-winston": "^3.1.0", "gelf": "^2.0.1", "gulp": "^4.0.2", @@ -118,14 +120,20 @@ "gulp-typedoc": "^2.2.2", "gulp-typescript": "^5.0.1", "helmet": "^3.16.0", + "inversify": "^5.0.1", + "inversify-binding-decorators": "^4.0.0", + "inversify-express-utils": "^6.3.2", "jsonschema": "^1.2.4", "jsonwebtoken": "^8.4.0", "morgan": "^1.9.1", "mysql2": "^1.6.5", "node-pre-gyp": "^0.14.0", "redis": "^2.8.0", + "reflect-metadata": "^0.1.13", "safe-squel": "^5.12.4", + "swagger-ui-express": "^4.1.4", "ts-graylog": "^1.0.2", + "tsoa": "^3.2.1", "winston": "^3.1.0" } } diff --git a/specification.yaml b/specification.yaml deleted file mode 100644 index 6f01a46..0000000 --- a/specification.yaml +++ /dev/null @@ -1,401 +0,0 @@ -openapi: "3.0.0" -info: - version: 1.0.0 - title: ugamela api - contact: - email: "apiteam@swagger.io" - license: - name: AGPL - url: "https://www.gnu.org/licenses/agpl-3.0.en.html" -servers: - - url: https://api.mamen.at/v1/ -paths: - /player/get/: - get: - summary: Get a player by his ID - operationId: getUser - tags: - - Player - parameters: - - name: id - in: query - required: true - description: The ID of the player - schema: - type: integer - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/PlayerGet" - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Authentication failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '404': - description: Player not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /player/create: - post: - summary: Creates a new player - operationId: createPlayer - tags: - - Player - requestBody: - $ref: '#/components/requestBodies/PlayerPost' - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Success" - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Authentication failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /planet/get: - get: - summary: Get a specific planet - operationId: getPlanet - tags: - - Planet - parameters: - - name: id - in: query - required: true - description: The ID of the planet - schema: - type: integer - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - oneOf: - - $ref: "#/components/schemas/PlanetGet" - - $ref: "#/components/schemas/PlanetSelfGet" - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Authentication failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '404': - description: Player not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /planet/create: - post: - summary: Creates a new planet - operationId: createPlanet - tags: - - Planet - requestBody: - $ref: '#/components/requestBodies/PlanetPost' - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - $ref: "#/components/schemas/Success" - '400': - description: Bad request - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '401': - description: Authentication failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /building/get/{planetID}: - get: - summary: Get all buildings on a planet. - operationId: getAllBuildings - tags: - - Building - parameters: - - name: planetID - in: path - required: true - description: The ID of the planet - schema: - type: integer - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - oneOf: - - $ref: "#/components/schemas/PlanetGet" - - $ref: "#/components/schemas/PlanetSelfGet" - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Authentication failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '404': - description: Player not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /building/get/{planetID}/{buildingID}: - get: - summary: Get a specific building on a planet. - operationId: getBuilding - tags: - - Building - parameters: - - name: planetID - in: path - required: true - description: The ID of the planet - schema: - type: integer - - name: buildingID - in: path - required: true - description: The ID of the building - schema: - type: integer - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - oneOf: - - $ref: "#/components/schemas/PlanetGet" - - $ref: "#/components/schemas/PlanetSelfGet" - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Authentication failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - '404': - description: Player not found - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - /building/build: - post: - summary: Get a specific building on a planet. - operationId: buildBuilding - tags: - - Building - parameters: - - name: planetID - in: path - required: true - description: The ID of the planet - schema: - type: integer - - name: buildingID - in: path - required: true - description: The ID of the building - schema: - type: integer - responses: - '200': - description: Expected response to a valid request - content: - application/json: - schema: - oneOf: - - $ref: "#/components/schemas/PlanetGet" - - $ref: "#/components/schemas/PlanetSelfGet" - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - '401': - description: Authentication failed - content: - application/json: - schema: - $ref: "#/components/schemas/Error" - # /technology/get: - # /technology/get/{techID}: - # /technology/build: - # /ship/get: - # /ship/get/{shipID}: - # /ship/build: - # /defense/get: - # /defense/get/{defenseID}: - # /defense/build: - # /alliance/get: - # /galaxy/get: - # /message/get: - # /message/send: - # /stats/get: - # /event/get: - # /event/create: - # /search: - # /settings/get: - # /settings/set: -components: - schemas: - Success: - properties: - message: - type: string - PlayerGet: - required: - - id - - username - properties: - id: - type: integer - format: int64 - username: - type: string - PlayerPost: - required: - - username - properties: - username: - type: string - email: - type: string - password: - type: string - PlanetGet: - properties: - id: - type: integer - format: int64 - ownerID: - type: integer - format: int64 - name: - type: string - posGalaxy: - type: integer - format: int32 - posSystem: - type: integer - format: int32 - posPlanet: - type: integer - format: int32 - planetType: - type: integer - format: int32 - image: - type: integer - format: int32 - destroyed: - type: boolean - PlanetSelfGet: - properties: - id: - type: integer - format: int64 - ownerID: - type: integer - format: int64 - name: - type: string - posGalaxy: - type: integer - format: int32 - posSystem: - type: integer - format: int32 - posPlanet: - type: integer - format: int32 - PlanetPost: - required: - - id - - username - properties: - id: - type: integer - format: int64 - username: - type: string - password: - type: string - email: - type: string - Error: - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string - - requestBodies: - PlayerPost: - description: A JSON object containing player information - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PlayerPost' - PlanetPost: - description: A JSON object containing player information - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/PlanetPost' diff --git a/src/App.spec.ts b/src/App.spec.ts deleted file mode 100644 index 3929791..0000000 --- a/src/App.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as chai from "chai"; -import chaiHttp = require("chai-http"); - -import App from "./App"; -import { Globals } from "./common/Globals"; -import JwtHelper from "./common/JwtHelper"; -import SimpleLogger from "./loggers/SimpleLogger"; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("./ioc/createContainer"); - -const logger = new SimpleLogger(); - -const app = new App(createContainer(), logger).express; - -chai.use(chaiHttp); -const expect = chai.expect; - -let authToken = ""; -let request = chai.request(app); - -describe("App", () => { - before(() => { - return request - .post("/v1/auth/login") - .send({ email: "user_1501005189510@test.com ", password: "admin" }) - .then(res => { - authToken = res.body.token; - }); - }); - - beforeEach(function() { - request = chai.request(app); - return; - }); - - it("should fail (not authorized)", () => { - const planetID = 167546850; - - return request.get(`/v1/buildings/${planetID}`).then(res => { - expect(res.status).to.equals(Globals.Statuscode.NOT_AUTHORIZED); - expect(res.body.error).to.be.equals("Authentication failed"); - }); - }); - - it("should fail (invalid userID)", () => { - const planetID = 167546850; - return request - .get(`/v1/buildings/${planetID}`) - .set("Authorization", JwtHelper.generateToken(parseInt("iAmNotAValidUserId", 10))) - .then(res => { - expect(res.status).to.equals(Globals.Statuscode.NOT_AUTHORIZED); - expect(res.body.error).to.be.equals("Authentication failed"); - }); - }); - - it("should fail (route does not exist)", () => { - return request - .get("/v1/idontexist") - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.equals(Globals.Statuscode.NOT_FOUND); - expect(res.body.error).to.be.equals("The route does not exist"); - }); - }); -}); diff --git a/src/App.ts b/src/App.ts index cecc178..792a0c9 100644 --- a/src/App.ts +++ b/src/App.ts @@ -1,251 +1,127 @@ import * as bodyParser from "body-parser"; import * as express from "express"; -import * as cors from "cors"; -import { Router } from "express"; -import JwtHelper from "./common/JwtHelper"; -import IAuthorizedRequest from "./interfaces/IAuthorizedRequest"; -import { Globals } from "./common/Globals"; -import IJwt from "./interfaces/IJwt"; +import { Response as ExResponse, Request as ExRequest, NextFunction } from "express"; + import ILogger from "./interfaces/ILogger"; -import RequestLogger from "./loggers/RequestLogger"; -import InputValidator from "./common/InputValidator"; -import AuthRouter from "./routes/AuthRouter"; -import BuildingRouter from "./routes/BuildingsRouter"; -import ConfigRouter from "./routes/ConfigRouter"; -import DefenseRouter from "./routes/DefenseRouter"; -import EventRouter from "./routes/EventRouter"; -import GalaxyRouter from "./routes/GalaxyRouter"; -import MessagesRouter from "./routes/MessagesRouter"; -import PlanetRouter from "./routes/PlanetsRouter"; -import UsersRouter from "./routes/UsersRouter"; -import ShipsRouter from "./routes/ShipsRouter"; -import TechsRouter from "./routes/TechsRouter"; import * as dotenv from "dotenv"; import * as helmet from "helmet"; -import * as apiConfig from "./config/apiconfig.json"; -import * as winston from "winston"; -import * as expressWinston from "express-winston"; - -dotenv.config(); +import { RegisterRoutes } from "./tsoa/routes"; +import { inject } from "inversify"; +import TYPES from "./ioc/types"; -const { format } = winston; -const { combine, printf } = format; - -const logFormat = printf(({ message, timestamp }) => { - return `${timestamp} [REQUEST] ${message}`; -}); +import { expressCspHeader, INLINE, SELF } from "express-csp-header"; +import { ValidateError } from "tsoa"; +import { Globals } from "./common/Globals"; -const productionMode = process.env.NODE_ENV === "production"; +dotenv.config(); -/** - * Creates and configures an ExpressJS web server. - */ export default class App { - public express: express.Application; - public userID: string; - public container; - private logger: ILogger; - - /** - * Creates and configures a new App-instance - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.logger = logger; - this.container = container; + public express: express.Express; + @inject(TYPES.ILogger) private logger: ILogger; + + public constructor() { this.express = express(); - this.middleware(); - this.routes(); - } - /** - * Registers middleware - */ - private middleware(): void { - this.express.use(bodyParser.json()); - this.express.use(bodyParser.urlencoded({ extended: false })); - - /** - * Check if given origin is whitelisted - * @param checkOrigin - * @param callback - */ - function corsHandler(checkOrigin: string, callback) { - if (apiConfig.cors.whitelist.indexOf(checkOrigin) === -1 && checkOrigin) { - return callback(new Error(`CORS "${checkOrigin}" is not whitelisted`)); - } - callback(null, true); - } + this.allowCors(); + this.middleware(); + this.startSwagger(); this.express.use(function(req, res, next) { - cors({ - origin: productionMode ? corsHandler : req.headers.origin, - allowedHeaders: ["Content-Type", "Authorization"], - methods: ["GET", "POST"], - credentials: true, - })(req, res, next); + res.header("Content-Type", "application/json"); + next(); }); - this.express.use(helmet.hidePoweredBy()); - this.express.use(helmet.noCache()); - this.express.use( - helmet.contentSecurityPolicy({ - directives: { - defaultSrc: ["'self'"], - }, - }), - ); + RegisterRoutes(this.express); + + this.registerErrorHandler(); + + this.registerNotFoundHandler(); } - /** - * Configure API endpoints - */ - private routes(): void { - const self = this; - - this.express.use("/*", (request: IAuthorizedRequest, response, next) => { - try { - this.logger.info( - "{" + - `'ip': '${request.headers["x-real-ip"] || request.connection.remoteAddress}', ` + - `'method': '${request.method}', ` + - `'url': '${request.url}', ` + - `'userID': '${request.userID}', ` + - "'params': { " + - `'query:': ${JSON.stringify(request.params || {}).replace(/(,\"password\":)(\")(.*)(\")/g, "")}, ` + - `'body': ${JSON.stringify(request.body || {}).replace(/(,\"password\":)(\")(.*)(\")/g, "")}` + - "}" + - "}", - ); - - // if the user tries to authenticate, we don't have a token yet - if ( - !request.originalUrl.toString().includes("/auth/") && - !request.originalUrl.toString().includes("/users/create/") && - !request.originalUrl.toString().includes("/config/") - ) { - const authString = request.header("authorization"); - - if ( - !InputValidator.isSet(authString) || - !authString.match("([a-zA-Z0-9\\-\\_]+\\.[a-zA-Z0-9\\-\\_]+\\.[a-zA-Z0-9\\-\\_]+)") - ) { - return response.status(Globals.Statuscode.NOT_AUTHORIZED).json({ - error: "Authentication failed", - }); - } - - const token: string = authString.match("([a-zA-Z0-9\\-\\_]+\\.[a-zA-Z0-9\\-\\_]+\\.[a-zA-Z0-9\\-\\_]+)")[0]; - - const payload: IJwt = JwtHelper.validateToken(token); - - if (InputValidator.isSet(payload) && InputValidator.isSet(payload.userID)) { - self.userID = payload.userID.toString(10); - - // check if userID is a valid integer - if (isNaN(parseInt(self.userID, 10))) { - return response.status(Globals.Statuscode.NOT_AUTHORIZED).json({ - error: "Invalid parameter", - }); - } else { - next(); - } - } else { - return response.status(Globals.Statuscode.NOT_AUTHORIZED).json({ - error: "Authentication failed", - }); - } - } else { - next(); - } - } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "Internal server error", + private registerErrorHandler() { + this.express.use(function ErrorHandler( + err: unknown, + req: ExRequest, + res: ExResponse, + next: NextFunction, + ): ExResponse | void { + if (err instanceof ValidateError) { + console.warn(`Caught Validation Error for ${req.path}:`, err.fields); + return res.status(Globals.StatusCodes.BAD_REQUEST).json({ + error: "Validation failed", + details: err?.fields, + }); + } + if (err instanceof Error) { + return res.status(Globals.StatusCodes.SERVER_ERROR).json({ + error: "Internal Server Error", }); } + + next(); }); + } - expressWinston.bodyBlacklist.push("password"); + private registerNotFoundHandler() { + this.express.use(function(req, res) { + res.status(404); - // TODO: find better method to filter out passwords in requests + res.send({ error: "Not found" }); + }); + } + + private startSwagger(): void { this.express.use( - expressWinston.logger({ - transports: [ - new winston.transports.Console(), - new winston.transports.File({ filename: `${RequestLogger.getPath()}access.log` }), - ], - format: combine( - format.timestamp({ - format: "YYYY-MM-DD HH:mm:ss", - }), - logFormat, - ), - maxsize: 10, - msg: - "{" + - "'ip': '{{(req.headers['x-real-ip'] || req.connection.remoteAddress)}}', " + - "'userID': '{{req.userID}}', " + - "'method': '{{req.method}}', " + - "'url': '{{req.url}}', " + - "'params': { " + - "'query:': {{JSON.stringify(req.params || {}).replace(/(,\"password\":)(\")(.*)(\")/g, '') }}, " + - "'body': {{JSON.stringify(req.body || {}).replace(/(,\"password\":)(\")(.*)(\")/g, '') }} " + - "}" + - "}", + expressCspHeader({ + directives: { + "default-src": [SELF, INLINE], + "script-src": [SELF, INLINE], + "style-src": [SELF, INLINE], + "img-src": ["data:", "*"], + "worker-src": [SELF, INLINE, "*"], + "block-all-mixed-content": true, + }, }), ); - this.register("/v1/config", new ConfigRouter(this.logger).router); - - this.register("/v1/auth", new AuthRouter(this.container, this.logger).router); - - this.register("/v1/user", new UsersRouter(this.container, this.logger).router); - - this.register("/v1/users", new UsersRouter(this.container, this.logger).router); - - this.register("/v1/planet", new PlanetRouter(this.container, this.logger).router); - - this.register("/v1/planets", new PlanetRouter(this.container, this.logger).router); - - this.register("/v1/buildings", new BuildingRouter(this.container, this.logger).router); - - this.register("/v1/techs", new TechsRouter(this.container, this.logger).router); - - this.register("/v1/ships", new ShipsRouter(this.container, this.logger).router); - - this.register("/v1/defenses", new DefenseRouter(this.container, this.logger).router); - - this.register("/v1/events", new EventRouter(this.container, this.logger).router); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const swaggerDocument = require("./tsoa/swagger.json"); + // eslint-disable-next-line @typescript-eslint/no-var-requires + const swaggerUi = require("swagger-ui-express"); - this.register("/v1/galaxy", new GalaxyRouter(this.container, this.logger).router); - - this.register("/v1/messages", new MessagesRouter(this.container, this.logger).router); + this.express.use("/v1/swagger", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); + } - this.express.use(function(request, response) { - return response.status(Globals.Statuscode.NOT_FOUND).json({ - error: "The route does not exist", - }); + private allowCors() { + this.express.use((req: express.Request, res: express.Response, next: express.NextFunction) => { + res.header("Access-Control-Allow-Origin", "*"); + res.header( + "Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept, Authorization, apikey, x-access-token", + ); + next(); }); } - /** - * Helper-function to register routes - * @param path the path of the route - * @param router the router which handles the requests to the given path - */ - private register(path: string, router: Router) { - const self = this; + private middleware(): void { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const noCache = require("nocache"); + + this.express.use( + bodyParser.urlencoded({ + extended: true, + }), + ); + this.express.use(bodyParser.json()); + this.express.use(helmet.hidePoweredBy()); + this.express.use(noCache()); this.express.use( - path, - function(req: IAuthorizedRequest, res, next) { - req.userID = self.userID; - next(); - }, - router, + helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'"], + }, + }), ); } } diff --git a/src/common/Calculations.ts b/src/common/Calculations.ts index 3af74ec..e073a7f 100644 --- a/src/common/Calculations.ts +++ b/src/common/Calculations.ts @@ -1,51 +1,34 @@ import ICoordinates from "../interfaces/ICoordinates"; -import ICosts from "../interfaces/ICosts"; +import IUnitCosts from "../interfaces/IUnitCosts"; import IPricelist from "../interfaces/IPricelist"; import IShipUnits from "../interfaces/IShipUnits"; import Config from "./Config"; import InputValidator from "./InputValidator"; -/** - * This class provides functionality for different common calculations - */ export default class Calculations { - /** - * Calculates the buildtime for a building, ship or defense in SECONDS and PER UNIT. - * @param metalCosts the metal-costs for the level/unit - * @param crystalCosts the crystal-costs for the level/unit - * @param robotFactory the current level of the robotic-factory - * @param naniteFactory the current level of the nanite-factory - * @returns number builtime in seconds - */ public static calculateBuildTimeInSeconds( metalCosts: number, crystalCosts: number, - robotFactory: number, - naniteFactory: number, + robotFactoryLevel: number, + naniteFactoryLevel: number, ): number { return Math.round( ((metalCosts + crystalCosts) / - (2500 * (1 + robotFactory) * 2 ** naniteFactory * Config.getGameConfig().server.speed)) * + (2500 * (1 + robotFactoryLevel) * 2 ** naniteFactoryLevel * Config.getGameConfig().server.speed)) * 3600, ); } - /** - * Calculates the research-time for a technology - * @param metalCosts the metal-costs for the level - * @param crystalCosts the crystal-costs for the level - * @param researchLab the current level of the reserach-lab - */ - public static calculateResearchTimeInSeconds(metalCosts: number, crystalCosts: number, researchLab: number): number { - return Math.round(((metalCosts + crystalCosts) / ((1 + researchLab) * Config.getGameConfig().server.speed)) * 3600); + public static calculateResearchTimeInSeconds( + metalCosts: number, + crystalCosts: number, + researchLabLevel: number, + ): number { + return Math.round( + ((metalCosts + crystalCosts) / ((1 + researchLabLevel) * Config.getGameConfig().server.speed)) * 3600, + ); } - /** - * Calculates the free missile slots - * @param siloLevel the level of the missile silo - * @param numAntiBallisticMissiles the amount of anti-ballistic missiles currently on the planet - * @param numInterplanetaryMissiles the amount of interplanetary missiles currently on the planet - */ public static calculateFreeMissileSlots( siloLevel: number, numAntiBallisticMissiles: number, @@ -54,14 +37,7 @@ export default class Calculations { return siloLevel * 10 - numAntiBallisticMissiles - numInterplanetaryMissiles * 2; } - /** - * Returns the costs of a unit. For building or technology, - * the costs for the next level is returned. - * For ships or defenses, the costs for one unit is returned. - * @param unitID - * @param currentLevel - */ - public static getCosts(unitID: number, currentLevel: number): ICosts { + public static getCosts(unitID: number, currentLevel: number): IUnitCosts { let costs: IPricelist; if (InputValidator.isValidBuildingId(unitID)) { @@ -86,12 +62,6 @@ export default class Calculations { }; } - /** - * Calculates the distances between two planets - * Source: http://www.owiki.de/index.php?title=Entfernung - * @param origin The first planet - * @param destination The second planet - */ public static calculateDistance(origin: ICoordinates, destination: ICoordinates): number { const distances = [ Math.abs(origin.posGalaxy - destination.posGalaxy), @@ -133,10 +103,6 @@ export default class Calculations { ); } - /** - * Returns the speed of the slowest ship in the fleet - * @param units The sent ship in this event - */ public static getSlowestShipSpeed(units: IShipUnits): number { const unitData = Config.getGameConfig(); diff --git a/src/common/Config.ts b/src/common/Config.ts index 4f72742..d47e192 100644 --- a/src/common/Config.ts +++ b/src/common/Config.ts @@ -1,43 +1,22 @@ -/** - * Helper-class to get the current game-configuration - */ import IGameConfig, { IBuilding, IDefense, IShip, ITechnology } from "../interfaces/IGameConfig"; -/** - * This class reads and returns the two main config files for the game. - */ export default class Config { - /** - * Returns the current configuration for the game - */ public static getGameConfig(): IGameConfig { return require("../config/game.json"); } - /** - * Returns a list of all buildings with their costs and cost-increase-factor per level - */ public static getBuildings(): IBuilding[] { return require("../config/game.json").units.buildings; } - /** - * Returns a list of all ships with their costs, rapidfire and properties like speed and capacity - */ public static getShips(): IShip[] { return require("../config/game.json").units.ships; } - /** - * Returns a list of all ships with their costs - */ public static getDefenses(): IDefense[] { return require("../config/game.json").units.defenses; } - /** - * Returns a list of all technologies with their costs and cost-increase-factor per level - */ public static getTechnologies(): ITechnology[] { return require("../config/game.json").units.technologies; } diff --git a/src/common/Database.ts b/src/common/Database.ts index 4b35f56..2823edc 100644 --- a/src/common/Database.ts +++ b/src/common/Database.ts @@ -3,22 +3,11 @@ import dotenv = require("dotenv"); dotenv.config(); -/** - * Manages the connection to the (mysql/mariaDB)-database - */ export default class Database { - /** - * Returns the connection-pool to the mysql-database - */ public static getConnectionPool() { return this.connectionPool; } - /** - * Returns a promise for a query - * @param sql the sql-query - * @param args optional arguments - */ public static query(sql: string) { // TODO: Log the mysql-errors return this.connectionPool.query(sql); diff --git a/src/common/Encryption.ts b/src/common/Encryption.ts index 3d2655c..6420473 100644 --- a/src/common/Encryption.ts +++ b/src/common/Encryption.ts @@ -8,21 +8,11 @@ try { bcrypt = require("bcryptjs"); } -/** - * This class contains functionality to hash passwords and check them - */ export default class Encryption { - /** - * Generates password hash - * @param password to hash - */ public static async hash(password: string): Promise { return bcrypt.hash(password, SALT_WORK_FACTOR); } - /** - * Compare password and hash - */ public static async compare(password: string, hash: string): Promise { return bcrypt.compare(password, hash); } diff --git a/src/common/ErrorHandler.ts b/src/common/ErrorHandler.ts new file mode 100644 index 0000000..4721fe4 --- /dev/null +++ b/src/common/ErrorHandler.ts @@ -0,0 +1,46 @@ +import { TsoaResponse } from "tsoa"; +import { Globals } from "./Globals"; +import FailureResponse from "../entities/responses/FailureResponse"; +import ApiException from "../exceptions/ApiException"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import { inject, injectable } from "inversify"; +import TYPES from "../ioc/types"; +import ILogger from "../interfaces/ILogger"; +import IErrorHandler from "../interfaces/IErrorHandler"; +import InvalidParameterException from "../exceptions/InvalidParameterException"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; + +@injectable() +export default class ErrorHandler implements IErrorHandler { + private logger: ILogger; + + constructor(@inject(TYPES.ILogger) logger: ILogger) { + this.logger = logger; + } + + public handle( + error: Error, + badRequestResponse: TsoaResponse, + unauthorizedResponse: TsoaResponse, + serverErrorResponse: TsoaResponse, + ) { + if ( + error instanceof ApiException || + error instanceof InvalidParameterException || + error instanceof NonExistingEntityException + ) { + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse(error.message)); + } + + if (error instanceof UnauthorizedException) { + return unauthorizedResponse(Globals.StatusCodes.NOT_AUTHORIZED, new FailureResponse(error.message)); + } + + this.logger.error(error.message, error.stack); + + return serverErrorResponse( + Globals.StatusCodes.SERVER_ERROR, + new FailureResponse("There was an error while handling the request."), + ); + } +} diff --git a/src/common/Globals.ts b/src/common/Globals.ts index 8b30725..279b783 100644 --- a/src/common/Globals.ts +++ b/src/common/Globals.ts @@ -1,6 +1,3 @@ -/** - * This class holds all global variables - */ class Globals { public static MIN_BUILDING_ID = 1; public static MAX_BUILDING_ID = 15; @@ -34,10 +31,7 @@ namespace Globals { MISSILE_SILO = 15, } - // 4xx - authentication failure - // 5xx - server errors - - export enum Statuscode { + export enum StatusCodes { SUCCESS = 200, CREATED = 201, BAD_REQUEST = 400, diff --git a/src/common/InputValidator.spec.ts b/src/common/InputValidator.spec.ts index eeb8515..0f16476 100644 --- a/src/common/InputValidator.spec.ts +++ b/src/common/InputValidator.spec.ts @@ -73,26 +73,26 @@ describe("InputValidator", function() { }); it("Valid build-order (building)", function() { - assert.equal(InputValidator.isValidBuildOrder({}, Globals.UnitType.BUILDING), null); + assert.equal(InputValidator.isValidBuildOrder([], Globals.UnitType.BUILDING), null); }); it("Valid build-order (technology)", function() { - assert.equal(InputValidator.isValidBuildOrder({}, Globals.UnitType.TECHNOLOGY), null); + assert.equal(InputValidator.isValidBuildOrder([], Globals.UnitType.TECHNOLOGY), null); }); it("Valid build-order (ship)", function() { - assert.equal(InputValidator.isValidBuildOrder({ 201: 1 }, Globals.UnitType.SHIP), true); + assert.equal(InputValidator.isValidBuildOrder([{ unitID: 201, amount: 1 }], Globals.UnitType.SHIP), true); }); it("Valid build-order (ship)", function() { - assert.equal(InputValidator.isValidBuildOrder({ 301: 1 }, Globals.UnitType.SHIP), false); + assert.equal(InputValidator.isValidBuildOrder([{ unitID: 301, amount: 1 }], Globals.UnitType.SHIP), false); }); it("Valid build-order (defense)", function() { - assert.equal(InputValidator.isValidBuildOrder({ 301: 1 }, Globals.UnitType.DEFENSE), true); + assert.equal(InputValidator.isValidBuildOrder([{ unitID: 301, amount: 1 }], Globals.UnitType.DEFENSE), true); }); it("Valid build-order (defense)", function() { - assert.equal(InputValidator.isValidBuildOrder({ 401: 1 }, Globals.UnitType.DEFENSE), false); + assert.equal(InputValidator.isValidBuildOrder([{ unitID: 401, amount: 1 }], Globals.UnitType.DEFENSE), false); }); }); diff --git a/src/common/InputValidator.ts b/src/common/InputValidator.ts index ff51774..2c5a4e6 100644 --- a/src/common/InputValidator.ts +++ b/src/common/InputValidator.ts @@ -1,14 +1,8 @@ import Config from "./Config"; import { Globals } from "./Globals"; +import BuildOrderItem from "../entities/common/BuildOrderItem"; -/** - * This class contains methods for input- and data-validation - */ export default class InputValidator { - /** - * Checks, if a given string is a valid integer - * @param input the input-string - */ public static isValidInt(input: string): boolean { if (!this.isSet(input)) { return false; @@ -21,10 +15,6 @@ export default class InputValidator { return input.match(/^-{0,1}\d+$/) !== null; } - /** - * Checks, if a given string is a valid float - * @param input the input-string - */ public static isValidFloat(input: string): boolean { if (!this.isSet(input)) { return false; @@ -37,10 +27,6 @@ export default class InputValidator { return input.match(/^\d+\.\d+$/) !== null; } - /** - * Checks, if a given string is a valid json-string - * @param input the input-string - */ public static isValidJson(input: string): boolean { try { JSON.parse(input); @@ -50,60 +36,36 @@ export default class InputValidator { return true; } - /** - * Checks, if a given input is defined and set - * @param input the input-string - */ public static isSet(input): boolean { return !(input === "" || typeof input === "undefined" || input === null || input.length === 0 || input === {}); } - /** - * Removes all special characters from a string as well as leading and trailing whitespaces - * @param input the input-string - */ public static sanitizeString(input: string): string { return input.replace(/[^a-z0-9@ .,_-]/gim, "").trim(); } - /** - * Checks, if a given unitID is a valid buildingID - * @param unitID a unitID - */ + public static isValidEmail(email: string): boolean { + const re = /\S+@\S+\.\S+/; + return re.test(email); + } + public static isValidBuildingId(unitID: number): boolean { return Globals.MIN_BUILDING_ID <= unitID && unitID <= Globals.MAX_BUILDING_ID; } - /** - * Checks, if a given unitID is a valid buildingID - * @param unitID a unitID - */ public static isValidDefenseId(unitID: number): boolean { return Globals.MIN_DEFENSE_ID <= unitID && unitID <= Globals.MAX_DEFENSE_ID; } - /** - * Checks, if a given unitID is a valid shipID - * @param unitID a unitID - */ public static isValidShipId(unitID: number): boolean { return Globals.MIN_SHIP_ID <= unitID && unitID <= Globals.MAX_SHIP_ID; } - /** - * Checks, if a given unitID is a valid technologyID - * @param unitID a unitID - */ public static isValidTechnologyId(unitID: number): boolean { return Globals.MIN_TECHNOLOGY_ID <= unitID && unitID <= Globals.MAX_TECHNOLOGY_ID; } - /** - * Checks, if the given build-order is valid for a given unit-type - * @param buildOrders an object representing a build-order - * @param unitType the type of the units in the build-order - */ - public static isValidBuildOrder(buildOrders: object, unitType: Globals.UnitType): boolean { + public static isValidBuildOrder(buildOrders: BuildOrderItem[], unitType: Globals.UnitType): boolean { let minID = 0; let maxID = 0; @@ -122,14 +84,8 @@ export default class InputValidator { break; } - for (const order in buildOrders) { - if ( - !InputValidator.isValidInt(order) || - !InputValidator.isValidInt(buildOrders[order]) || - parseInt(order, 10) < minID || - parseInt(order, 10) > maxID || - buildOrders[order] < 0 - ) { + for (const order of buildOrders) { + if (order.unitID < minID || order.unitID > maxID || order.amount < 0) { return false; } } @@ -137,12 +93,6 @@ export default class InputValidator { return true; } - /** - * Validates a given position. The planet-parameter is optional and will be set to 1 if no paramter is passed - * @param posGalaxy the galaxy-position - * @param posSystem the posSystem-position - * @param posPlanet the planet-position - */ public static isValidPosition(posGalaxy, posSystem, posPlanet = 1): boolean { return ( posGalaxy >= 1 && diff --git a/src/common/JwtHelper.ts b/src/common/JwtHelper.ts index 02624be..b1c491b 100644 --- a/src/common/JwtHelper.ts +++ b/src/common/JwtHelper.ts @@ -3,14 +3,7 @@ import IJwt from "../interfaces/IJwt"; // eslint-disable-next-line @typescript-eslint/no-var-requires const jwt = require("jsonwebtoken"); -/** - * This class contains functionality to generate and validate JWT-token - */ export default class JwtHelper { - /** - * Generates a new JWT-token containing the passed userID - * @param userID the userID of the authenticated user - */ public static generateToken(userID: number): string { return jwt.sign( { diff --git a/src/common/Queue.ts b/src/common/Queue.ts index 20a5258..5c4d578 100644 --- a/src/common/Queue.ts +++ b/src/common/Queue.ts @@ -1,22 +1,8 @@ import QueueItem from "./QueueItem"; -/** - * This class represents a Queue. - */ export default class Queue { - /** - * The last time, the queue was updated - */ private lastUpdateTime = 0; - - /** - * The remaining time for the queue - */ private timeRemaining = 0; - - /** - * The queue itself - */ private queue: QueueItem[] = []; /** @@ -27,31 +13,18 @@ export default class Queue { this.lastUpdateTime = updateTime; } - /** - * Returns the last time, the queue was updated - */ public getLastUpdateTime(): number { return this.lastUpdateTime; } - /** - * Sets the time remaining - * @param timeRemaining - */ public setTimeRemaining(timeRemaining: number) { this.timeRemaining = timeRemaining; } - /** - * Returns the time remaining - */ public getTimeRemaining(): number { return this.timeRemaining; } - /** - * Returns the queue - */ public getQueue(): QueueItem[] { return this.queue; } diff --git a/src/common/QueueItem.ts b/src/common/QueueItem.ts index d39808b..0cf048a 100644 --- a/src/common/QueueItem.ts +++ b/src/common/QueueItem.ts @@ -1,7 +1,3 @@ -/** - * This class represents a QueueItem. - * A QueueItem is part of a build-queue. - */ export default class QueueItem { public unitID: number; public amount: number; diff --git a/src/common/SerializationHelper.ts b/src/common/SerializationHelper.ts index 22c1e57..16f5efa 100644 --- a/src/common/SerializationHelper.ts +++ b/src/common/SerializationHelper.ts @@ -1,6 +1,3 @@ -/** - * Helper-class to create a instance of a class from a json-string - */ export default class SerializationHelper { /** * Takes an object and a json-string and returns a new instance of the given object diff --git a/src/entities/common/BuildOrderItem.ts b/src/entities/common/BuildOrderItem.ts new file mode 100644 index 0000000..51f8ae2 --- /dev/null +++ b/src/entities/common/BuildOrderItem.ts @@ -0,0 +1,4 @@ +export default class BuildOrderItem { + unitID: number; + amount: number; +} diff --git a/src/entities/requests/AuthRequest.ts b/src/entities/requests/AuthRequest.ts new file mode 100644 index 0000000..59d3725 --- /dev/null +++ b/src/entities/requests/AuthRequest.ts @@ -0,0 +1,4 @@ +export default class AuthRequest { + email: string; + password: string; +} diff --git a/src/entities/requests/BuildBuildingRequest.ts b/src/entities/requests/BuildBuildingRequest.ts new file mode 100644 index 0000000..5369d31 --- /dev/null +++ b/src/entities/requests/BuildBuildingRequest.ts @@ -0,0 +1,4 @@ +export default class BuildBuildingRequest { + planetID: number; + buildingID: number; +} diff --git a/src/entities/requests/BuildDefenseRequest.ts b/src/entities/requests/BuildDefenseRequest.ts new file mode 100644 index 0000000..48b7c7c --- /dev/null +++ b/src/entities/requests/BuildDefenseRequest.ts @@ -0,0 +1,6 @@ +import BuildOrderItem from "../common/BuildOrderItem"; + +export default class BuildDefenseRequest { + planetID: number; + buildOrder: BuildOrderItem[]; +} diff --git a/src/entities/requests/BuildShipsRequest.ts b/src/entities/requests/BuildShipsRequest.ts new file mode 100644 index 0000000..823c401 --- /dev/null +++ b/src/entities/requests/BuildShipsRequest.ts @@ -0,0 +1,6 @@ +import BuildOrderItem from "../common/BuildOrderItem"; + +export default class BuildShipsRequest { + planetID: number; + buildOrder: BuildOrderItem[]; +} diff --git a/src/entities/requests/BuildTechRequest.ts b/src/entities/requests/BuildTechRequest.ts new file mode 100644 index 0000000..6e9cbb4 --- /dev/null +++ b/src/entities/requests/BuildTechRequest.ts @@ -0,0 +1,4 @@ +export default class BuildTechRequest { + planetID: number; + techID: number; +} diff --git a/src/entities/requests/CancelBuildingRequest.ts b/src/entities/requests/CancelBuildingRequest.ts new file mode 100644 index 0000000..c0143de --- /dev/null +++ b/src/entities/requests/CancelBuildingRequest.ts @@ -0,0 +1,4 @@ +export default class CancelBuildingRequest { + planetID: number; + buildingID: number; +} diff --git a/src/entities/requests/CancelTechRequest.ts b/src/entities/requests/CancelTechRequest.ts new file mode 100644 index 0000000..1a18d93 --- /dev/null +++ b/src/entities/requests/CancelTechRequest.ts @@ -0,0 +1,3 @@ +export default class CancelTechRequest { + planetID: number; +} diff --git a/src/entities/requests/CreateUserRequest.ts b/src/entities/requests/CreateUserRequest.ts new file mode 100644 index 0000000..20ed83c --- /dev/null +++ b/src/entities/requests/CreateUserRequest.ts @@ -0,0 +1,5 @@ +export default class CreateUserRequest { + username: string; + email: string; + password: string; +} diff --git a/src/entities/requests/DeleteMessageRequest.ts b/src/entities/requests/DeleteMessageRequest.ts new file mode 100644 index 0000000..80e147f --- /dev/null +++ b/src/entities/requests/DeleteMessageRequest.ts @@ -0,0 +1,3 @@ +export default class DeleteMessageRequest { + messageID: number; +} diff --git a/src/entities/requests/DemolishBuildingRequest.ts b/src/entities/requests/DemolishBuildingRequest.ts new file mode 100644 index 0000000..eb681a0 --- /dev/null +++ b/src/entities/requests/DemolishBuildingRequest.ts @@ -0,0 +1,4 @@ +export default class DemolishBuildingRequest { + planetID: number; + buildingID: number; +} diff --git a/src/entities/requests/DestroyPlanetRequest.ts b/src/entities/requests/DestroyPlanetRequest.ts new file mode 100644 index 0000000..3ffc179 --- /dev/null +++ b/src/entities/requests/DestroyPlanetRequest.ts @@ -0,0 +1,3 @@ +export default class DestroyPlanetRequest { + planetID: number; +} diff --git a/src/entities/requests/RenamePlanetRequest.ts b/src/entities/requests/RenamePlanetRequest.ts new file mode 100644 index 0000000..f11ba3c --- /dev/null +++ b/src/entities/requests/RenamePlanetRequest.ts @@ -0,0 +1,4 @@ +export default class RenamePlanetRequest { + planetID: number; + newName: string; +} diff --git a/src/entities/requests/SendMessageRequest.ts b/src/entities/requests/SendMessageRequest.ts new file mode 100644 index 0000000..dcd53ee --- /dev/null +++ b/src/entities/requests/SendMessageRequest.ts @@ -0,0 +1,5 @@ +export default class SendMessageRequest { + receiverID: number; + subject: string; + body: string; +} diff --git a/src/entities/requests/SetCurrentPlanetRequest.ts b/src/entities/requests/SetCurrentPlanetRequest.ts new file mode 100644 index 0000000..1a3e915 --- /dev/null +++ b/src/entities/requests/SetCurrentPlanetRequest.ts @@ -0,0 +1,3 @@ +export default class SetCurrentPlanetRequest { + planetID: number; +} diff --git a/src/entities/requests/UpdateUserRequest.ts b/src/entities/requests/UpdateUserRequest.ts new file mode 100644 index 0000000..6e16952 --- /dev/null +++ b/src/entities/requests/UpdateUserRequest.ts @@ -0,0 +1,5 @@ +export default class UpdateUserRequest { + username?: string; + email?: string; + password?: string; +} diff --git a/src/entities/responses/AuthSuccessResponse.ts b/src/entities/responses/AuthSuccessResponse.ts new file mode 100644 index 0000000..d4d2996 --- /dev/null +++ b/src/entities/responses/AuthSuccessResponse.ts @@ -0,0 +1,3 @@ +export default class AuthSuccessResponse { + token: string; +} diff --git a/src/entities/responses/CreateUserResponse.ts b/src/entities/responses/CreateUserResponse.ts new file mode 100644 index 0000000..9e901e1 --- /dev/null +++ b/src/entities/responses/CreateUserResponse.ts @@ -0,0 +1,4 @@ +export default class CreateUserResponse { + userID: number; + token: string; +} diff --git a/src/entities/responses/FailureResponse.ts b/src/entities/responses/FailureResponse.ts new file mode 100644 index 0000000..b59d619 --- /dev/null +++ b/src/entities/responses/FailureResponse.ts @@ -0,0 +1,7 @@ +export default class FailureResponse { + error: string; + + constructor(error: string) { + this.error = error; + } +} diff --git a/src/exceptions/ApiException.ts b/src/exceptions/ApiException.ts new file mode 100644 index 0000000..e599156 --- /dev/null +++ b/src/exceptions/ApiException.ts @@ -0,0 +1,5 @@ +export default class ApiException extends Error { + public constructor(m: string) { + super(m); + } +} diff --git a/src/exceptions/DuplicateRecordException.ts b/src/exceptions/DuplicateRecordException.ts index e7e07ad..b228342 100644 --- a/src/exceptions/DuplicateRecordException.ts +++ b/src/exceptions/DuplicateRecordException.ts @@ -1,15 +1,5 @@ -/** - * This error should be thrown when a record already exists in the database. - */ export default class DuplicateRecordException extends Error { - /** - * Takes a message-string and returns a new DuplicateRecordException-object - * @param m the exception-message - */ public constructor(m: string) { super(m); - - // Set the prototype explicitly. - Object.setPrototypeOf(this, DuplicateRecordException.prototype); } } diff --git a/src/exceptions/EntityInvalidException.ts b/src/exceptions/EntityInvalidException.ts deleted file mode 100644 index ee777f6..0000000 --- a/src/exceptions/EntityInvalidException.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This error should be thrown when an entity is invalid. - */ -export default class EntityInvalidException extends Error { - /** - * Takes a message-string and returns a new EntityInvalidException-object - * @param m the exception-message - */ - public constructor(m: string) { - super(m); - - // Set the prototype explicitly. - Object.setPrototypeOf(this, EntityInvalidException.prototype); - } -} diff --git a/src/exceptions/InvalidParameterException.ts b/src/exceptions/InvalidParameterException.ts index 17352a0..b863867 100644 --- a/src/exceptions/InvalidParameterException.ts +++ b/src/exceptions/InvalidParameterException.ts @@ -1,15 +1,5 @@ -/** - * This error should be thrown when a parameter is invalid. - */ export default class InvalidParameterException extends Error { - /** - * Takes a message-string and returns a new InvalidParameterException-object - * @param m the exception-message - */ public constructor(m: string) { super(m); - - // Set the prototype explicitly. - Object.setPrototypeOf(this, InvalidParameterException.prototype); } } diff --git a/src/exceptions/NonExistingEntityException.ts b/src/exceptions/NonExistingEntityException.ts new file mode 100644 index 0000000..3c7a66a --- /dev/null +++ b/src/exceptions/NonExistingEntityException.ts @@ -0,0 +1,5 @@ +export default class NonExistingEntityException extends Error { + public constructor(m: string) { + super(m); + } +} diff --git a/src/exceptions/UnauthorizedException.ts b/src/exceptions/UnauthorizedException.ts new file mode 100644 index 0000000..aa0605c --- /dev/null +++ b/src/exceptions/UnauthorizedException.ts @@ -0,0 +1,5 @@ +export default class UnauthorizedException extends Error { + public constructor(m: string) { + super(m); + } +} diff --git a/src/index.ts b/src/index.ts index e7c555f..3837870 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,13 @@ import * as http from "http"; import App from "./App"; -import Container from "./ioc/container"; import ILogger from "./interfaces/ILogger"; import SimpleLogger from "./loggers/SimpleLogger"; -const createContainer = require("./ioc/createContainer"); - -const container: Container = createContainer(); - const logger: ILogger = new SimpleLogger(); const port = process.env.PORT || 3000; -const app = new App(container, logger); +const app = new App(); app.express.set("port", port); diff --git a/src/interfaces/IBuildingService.ts b/src/interfaces/IBuildingService.ts deleted file mode 100644 index 1d7eb6f..0000000 --- a/src/interfaces/IBuildingService.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Buildings from "../units/Buildings"; - -export default interface IBuildingService { - getBuildings(planetID: number): Promise; - createBuildingsRow(planetID: number, connection); -} diff --git a/src/interfaces/IDefenseService.ts b/src/interfaces/IDefenseService.ts deleted file mode 100644 index 3774723..0000000 --- a/src/interfaces/IDefenseService.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Defenses from "../units/Defenses"; - -export default interface IDefenseService { - createDefenseRow(planetID: number, connection); - getDefenses(userID: number, planetID: number): Promise; -} diff --git a/src/interfaces/IErrorHandler.ts b/src/interfaces/IErrorHandler.ts new file mode 100644 index 0000000..fb0d472 --- /dev/null +++ b/src/interfaces/IErrorHandler.ts @@ -0,0 +1,12 @@ +import { TsoaResponse } from "tsoa"; +import { Globals } from "../common/Globals"; +import FailureResponse from "../entities/responses/FailureResponse"; + +export default interface IErrorHandler { + handle( + error: Error, + badRequestResponse: TsoaResponse, + unauthorizedResponse: TsoaResponse, + serverErrorResponse: TsoaResponse, + ); +} diff --git a/src/interfaces/IEventService.ts b/src/interfaces/IEventService.ts deleted file mode 100644 index 66bff0d..0000000 --- a/src/interfaces/IEventService.ts +++ /dev/null @@ -1,7 +0,0 @@ -import Event from "../units/Event"; - -export default interface IEventService { - createNewEvent(event: Event); - getEventOfPlayer(userID: number, eventID: number): Promise; - cancelEvent(event: Event); -} diff --git a/src/interfaces/IGalaxyService.ts b/src/interfaces/IGalaxyService.ts deleted file mode 100644 index 2e0c8b3..0000000 --- a/src/interfaces/IGalaxyService.ts +++ /dev/null @@ -1,7 +0,0 @@ -import ICoordinates from "./ICoordinates"; - -export default interface IGalaxyService { - getFreePosition(maxGalaxy: number, maxSystem: number, minPlanet: number, maxPlanet: number): Promise; - createGalaxyRow(planetID: number, posGalaxy: number, posSystem: number, posPlanet: number, connection); - getGalaxyInfo(posGalaxy: number, posSystem: number); -} diff --git a/src/interfaces/IMessageService.ts b/src/interfaces/IMessageService.ts deleted file mode 100644 index 188fcab..0000000 --- a/src/interfaces/IMessageService.ts +++ /dev/null @@ -1,8 +0,0 @@ -import Message from "../units/Message"; - -export default interface IMessageService { - getAllMessages(userID: number); - getMessageById(userID: number, messageID: number): Promise; - deleteMessage(userID: number, messageID: number); - sendMessage(senderID: number, receiverID: number, subject: string, messageText: string); -} diff --git a/src/interfaces/IPlanetService.ts b/src/interfaces/IPlanetService.ts deleted file mode 100644 index e9385a5..0000000 --- a/src/interfaces/IPlanetService.ts +++ /dev/null @@ -1,13 +0,0 @@ -import Planet from "../units/Planet"; -import ICoordinates from "./ICoordinates"; - -export default interface IPlanetService { - getPlanet(userID: number, planetID: number, fullInfo?: boolean): Promise; - updatePlanet(planet: Planet): Promise; - getNewId(): Promise; - createNewPlanet(planet: Planet, connection?); - getAllPlanetsOfUser(userID: number, fullInfo?: boolean); - getMovementOnPlanet(userID: number, planetID: number); - deletePlanet(userID: number, planetID: number); - getPlanetOrMoonAtPosition(position: ICoordinates): Promise; -} diff --git a/src/interfaces/IShipService.ts b/src/interfaces/IShipService.ts deleted file mode 100644 index 04f7159..0000000 --- a/src/interfaces/IShipService.ts +++ /dev/null @@ -1,4 +0,0 @@ -export default interface IShipService { - createShipsRow(planetID: number, connection); - getShips(userID: number, planetID: number); -} diff --git a/src/interfaces/ITechService.ts b/src/interfaces/ITechService.ts deleted file mode 100644 index c40ed79..0000000 --- a/src/interfaces/ITechService.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Techs from "../units/Techs"; - -export default interface ITechService { - createTechRow(userID: number, connection); - getTechs(userID: number): Promise; -} diff --git a/src/interfaces/ICosts.ts b/src/interfaces/IUnitCosts.ts similarity index 66% rename from src/interfaces/ICosts.ts rename to src/interfaces/IUnitCosts.ts index 8670b13..43963cc 100644 --- a/src/interfaces/ICosts.ts +++ b/src/interfaces/IUnitCosts.ts @@ -1,4 +1,4 @@ -export default interface ICosts { +export default interface IUnitCosts { metal: number; crystal: number; deuterium: number; diff --git a/src/interfaces/IUserService.ts b/src/interfaces/IUserService.ts deleted file mode 100644 index 2209eee..0000000 --- a/src/interfaces/IUserService.ts +++ /dev/null @@ -1,11 +0,0 @@ -import User from "../units/User"; - -export default interface IUserService { - getAuthenticatedUser(userID: number): Promise; - getUserById(userID: number): Promise; - getUserForAuthentication(email: string): Promise; - checkIfNameOrMailIsTaken(username: string, email: string); - getNewId(): Promise; - createNewUser(user: User, connection?); - updateUserData(user: User, connection?); -} diff --git a/src/interfaces/repositories/IBuildingRepository.ts b/src/interfaces/repositories/IBuildingRepository.ts new file mode 100644 index 0000000..884a328 --- /dev/null +++ b/src/interfaces/repositories/IBuildingRepository.ts @@ -0,0 +1,6 @@ +import IRepository from "./IRepository"; +import Buildings from "../../units/Buildings"; + +export default interface IBuildingRepository extends IRepository { + createTransactional(planetID: number, connection): Promise; +} diff --git a/src/interfaces/repositories/IDefenseRepository.ts b/src/interfaces/repositories/IDefenseRepository.ts new file mode 100644 index 0000000..95c6c89 --- /dev/null +++ b/src/interfaces/repositories/IDefenseRepository.ts @@ -0,0 +1,6 @@ +import IRepository from "./IRepository"; +import Defenses from "../../units/Defenses"; + +export default interface IDefenseRepository extends IRepository { + createTransactional(planetID: number, connection): Promise; +} diff --git a/src/interfaces/repositories/IGalaxyRepository.ts b/src/interfaces/repositories/IGalaxyRepository.ts new file mode 100644 index 0000000..e39c795 --- /dev/null +++ b/src/interfaces/repositories/IGalaxyRepository.ts @@ -0,0 +1,14 @@ +import GalaxyPositionInfo from "../../units/GalaxyPositionInfo"; +import ICoordinates from "../ICoordinates"; +import GalaxyRow from "../../units/GalaxyRow"; + +export default interface IGalaxyRepository { + getPositionInfo(posGalaxy: number, posSystem: number): Promise; + getFreePosition( + maxGalaxy: number, + maxSystem: number, + minPlanetPos: number, + maxPlanetPos: number, + ): Promise; + createTransactional(row: GalaxyRow, connection): Promise; +} diff --git a/src/interfaces/repositories/IMessageRepository.ts b/src/interfaces/repositories/IMessageRepository.ts new file mode 100644 index 0000000..bd1277e --- /dev/null +++ b/src/interfaces/repositories/IMessageRepository.ts @@ -0,0 +1,7 @@ +import IRepository from "./IRepository"; +import Message from "../../units/Message"; + +export default interface IMessageRepository extends IRepository { + getAll(userID: number): Promise; + remove(userID: number, messageID: number): Promise; +} diff --git a/src/interfaces/repositories/IPlanetRepository.ts b/src/interfaces/repositories/IPlanetRepository.ts new file mode 100644 index 0000000..34573d6 --- /dev/null +++ b/src/interfaces/repositories/IPlanetRepository.ts @@ -0,0 +1,11 @@ +import IRepository from "./IRepository"; +import Planet from "../../units/Planet"; +import Event from "../../units/Event"; + +export default interface IPlanetRepository extends IRepository { + getAllOfUser(userID): Promise; + getMovement(userID: number, planetID: number): Promise; + delete(planetID: number, userID: number): Promise; + getNewId(): Promise; + createTransactional(t: Planet, connection): Promise; +} diff --git a/src/interfaces/repositories/IRepository.ts b/src/interfaces/repositories/IRepository.ts new file mode 100644 index 0000000..f480da3 --- /dev/null +++ b/src/interfaces/repositories/IRepository.ts @@ -0,0 +1,6 @@ +export default interface IRepository { + exists(id: number): Promise; + getById(id: number): Promise; + save(t: T): Promise; + create(t: T): Promise; +} diff --git a/src/interfaces/repositories/IShipsRepository.ts b/src/interfaces/repositories/IShipsRepository.ts new file mode 100644 index 0000000..469a971 --- /dev/null +++ b/src/interfaces/repositories/IShipsRepository.ts @@ -0,0 +1,6 @@ +import IRepository from "./IRepository"; +import Ships from "../../units/Ships"; + +export default interface IShipsRepository extends IRepository { + createTransactional(planetID: number, connection): Promise; +} diff --git a/src/interfaces/repositories/ITechnologiesRepository.ts b/src/interfaces/repositories/ITechnologiesRepository.ts new file mode 100644 index 0000000..d9c853c --- /dev/null +++ b/src/interfaces/repositories/ITechnologiesRepository.ts @@ -0,0 +1,6 @@ +import IRepository from "./IRepository"; +import Techs from "../../units/Techs"; + +export default interface ITechnologiesRepository extends IRepository { + createTransactional(userID: number, connection): Promise; +} diff --git a/src/interfaces/repositories/IUserRepository.ts b/src/interfaces/repositories/IUserRepository.ts new file mode 100644 index 0000000..148f822 --- /dev/null +++ b/src/interfaces/repositories/IUserRepository.ts @@ -0,0 +1,10 @@ +import IRepository from "./IRepository"; +import User from "../../units/User"; + +export default interface IUserRepository extends IRepository { + getUserForAuthentication(email: string): Promise; + checkUsernameTaken(username: string): Promise; + checkEmailTaken(email: string): Promise; + getNewId(): Promise; + createTransactional(t: User, connection): Promise; +} diff --git a/src/interfaces/services/IAuthService.ts b/src/interfaces/services/IAuthService.ts new file mode 100644 index 0000000..d71025e --- /dev/null +++ b/src/interfaces/services/IAuthService.ts @@ -0,0 +1,3 @@ +export default interface IAuthService { + authenticateUser(email: string, password: string): Promise; +} diff --git a/src/interfaces/services/IBuildingService.ts b/src/interfaces/services/IBuildingService.ts new file mode 100644 index 0000000..b4c529e --- /dev/null +++ b/src/interfaces/services/IBuildingService.ts @@ -0,0 +1,11 @@ +import Planet from "../../units/Planet"; +import BuildBuildingRequest from "../../entities/requests/BuildBuildingRequest"; +import Buildings from "../../units/Buildings"; +import DemolishBuildingRequest from "../../entities/requests/DemolishBuildingRequest"; + +export default interface IBuildingService { + start(request: BuildBuildingRequest, userID: number): Promise; + getAll(planetID: number, userID: number): Promise; + cancel(planetID: number, userID: number): Promise; + demolish(request: DemolishBuildingRequest, userID: number): Promise; +} diff --git a/src/interfaces/services/IDefenseService.ts b/src/interfaces/services/IDefenseService.ts new file mode 100644 index 0000000..2c9b1c6 --- /dev/null +++ b/src/interfaces/services/IDefenseService.ts @@ -0,0 +1,8 @@ +import Defenses from "../../units/Defenses"; +import BuildDefenseRequest from "../../entities/requests/BuildDefenseRequest"; +import Planet from "../../units/Planet"; + +export default interface IDefenseService { + getAll(userID: number, planetID: number): Promise; + processBuildOrder(request: BuildDefenseRequest, userID: number): Promise; +} diff --git a/src/interfaces/services/IEventService.ts b/src/interfaces/services/IEventService.ts new file mode 100644 index 0000000..5e517b8 --- /dev/null +++ b/src/interfaces/services/IEventService.ts @@ -0,0 +1,7 @@ +import Event from "../../units/Event"; + +export default interface IEventService { + create(event: Event); + getEvent(userID: number, eventID: number): Promise; + cancel(event: Event); +} diff --git a/src/interfaces/services/IGalaxyService.ts b/src/interfaces/services/IGalaxyService.ts new file mode 100644 index 0000000..9954564 --- /dev/null +++ b/src/interfaces/services/IGalaxyService.ts @@ -0,0 +1,7 @@ +import GalaxyPositionInfo from "../../units/GalaxyPositionInfo"; +import ICoordinates from "../ICoordinates"; + +export default interface IGalaxyService { + getPositionInfo(posGalaxy: number, posSystem: number): Promise; + getFreePosition(): Promise; +} diff --git a/src/interfaces/services/IMessageService.ts b/src/interfaces/services/IMessageService.ts new file mode 100644 index 0000000..ebd897b --- /dev/null +++ b/src/interfaces/services/IMessageService.ts @@ -0,0 +1,9 @@ +import Message from "../../units/Message"; +import SendMessageRequest from "../../entities/requests/SendMessageRequest"; + +export default interface IMessageService { + getAll(userID: number): Promise; + getById(messageID: number, userID: number): Promise; + send(request: SendMessageRequest, userID: number): Promise; + delete(messageID: number, userID: number): Promise; +} diff --git a/src/interfaces/services/IPlanetService.ts b/src/interfaces/services/IPlanetService.ts new file mode 100644 index 0000000..ee4f8e1 --- /dev/null +++ b/src/interfaces/services/IPlanetService.ts @@ -0,0 +1,12 @@ +import Planet from "../../units/Planet"; +import Event from "../../units/Event"; +import RenamePlanetRequest from "../../entities/requests/RenamePlanetRequest"; + +export default interface IPlanetService { + checkOwnership(userID: number, planetID: number): Promise; + getAll(userID: number): Promise; + getMovement(planetID: number, userID: number): Promise; + destroy(planetID: number, userID: number): Promise; + rename(request: RenamePlanetRequest, userID: number): Promise; + getById(planetID: number, userID: number): Promise; +} diff --git a/src/interfaces/services/IRequirementsService.ts b/src/interfaces/services/IRequirementsService.ts new file mode 100644 index 0000000..2567206 --- /dev/null +++ b/src/interfaces/services/IRequirementsService.ts @@ -0,0 +1,7 @@ +import { IRequirement } from "../IGameConfig"; +import Buildings from "../../units/Buildings"; +import Techs from "../../units/Techs"; + +export default interface IRequirementsService { + fulfilled(requirements: IRequirement[], buildings: Buildings, technologies: Techs): boolean; +} diff --git a/src/interfaces/services/IShipService.ts b/src/interfaces/services/IShipService.ts new file mode 100644 index 0000000..7551556 --- /dev/null +++ b/src/interfaces/services/IShipService.ts @@ -0,0 +1,7 @@ +import BuildShipsRequest from "../../entities/requests/BuildShipsRequest"; +import Planet from "../../units/Planet"; + +export default interface IShipService { + getAll(userID: number, planetID: number); + processBuildOrder(request: BuildShipsRequest, userID: number): Promise; +} diff --git a/src/interfaces/services/ITechService.ts b/src/interfaces/services/ITechService.ts new file mode 100644 index 0000000..f000440 --- /dev/null +++ b/src/interfaces/services/ITechService.ts @@ -0,0 +1,10 @@ +import Techs from "../../units/Techs"; +import BuildTechRequest from "../../entities/requests/BuildTechRequest"; +import Planet from "../../units/Planet"; +import CancelTechRequest from "../../entities/requests/CancelTechRequest"; + +export default interface ITechService { + getAll(userID: number): Promise; + build(request: BuildTechRequest, userID: number): Promise; + cancel(request: CancelTechRequest, userID: number): Promise; +} diff --git a/src/interfaces/services/IUserService.ts b/src/interfaces/services/IUserService.ts new file mode 100644 index 0000000..19c03ef --- /dev/null +++ b/src/interfaces/services/IUserService.ts @@ -0,0 +1,14 @@ +import User from "../../units/User"; +import CreateUserRequest from "../../entities/requests/CreateUserRequest"; +import AuthSuccessResponse from "../../entities/responses/AuthSuccessResponse"; +import UpdateUserRequest from "../../entities/requests/UpdateUserRequest"; +import SetCurrentPlanetRequest from "../../entities/requests/SetCurrentPlanetRequest"; + +export default interface IUserService { + getUserForAuthentication(email: string): Promise; + getAuthenticatedUser(userID: number): Promise; + getOtherUser(userID: number): Promise; + create(request: CreateUserRequest): Promise; + update(request: UpdateUserRequest, userID: number): Promise; + setCurrentPlanet(request: SetCurrentPlanetRequest, userID: number): Promise; +} diff --git a/src/ioc/container.ts b/src/ioc/container.ts deleted file mode 100644 index 8b625c3..0000000 --- a/src/ioc/container.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * This class defines a container for IoC - * and dependency-injection functionality - */ -export default class Container { - /** - * All registered services - */ - private readonly services; - - /** - * Initializes the object with an empty list of services - */ - public constructor() { - this.services = {}; - } - - /** - * Returns a registered service by name. - * - * Example of registering: - * container.service("buildingService", () => new BuildingService()); - * - * Example of getting a registered service: - * const myService = container.buildingService; - * - * @param name the key for the registered service - * @param createService a function which creates a new service - */ - public service(name, createService) { - Object.defineProperty(this, name, { - get: () => { - if (!this.services.hasOwnProperty(name)) { - this.services[name] = createService(this); - } - - return this.services[name]; - }, - configurable: true, - enumerable: true, - }); - - return this; - } -} diff --git a/src/ioc/createContainer.ts b/src/ioc/createContainer.ts deleted file mode 100644 index 13dde9c..0000000 --- a/src/ioc/createContainer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import BuildingService from "../services/BuildingService"; -import DefenseService from "../services/DefenseService"; -import EventService from "../services/EventService"; -import Container from "./container"; -import GalaxyService from "../services/GalaxyService"; -import MessageService from "../services/MessageService"; -import PlanetService from "../services/PlanetService"; -import ShipService from "../services/ShipService"; -import TechService from "../services/TechService"; -import UserService from "../services/UserService"; - -module.exports = function() { - const container = new Container(); - - container.service("buildingService", () => new BuildingService()); - container.service("defenseService", () => new DefenseService()); - container.service("eventService", () => new EventService()); - container.service("galaxyService", () => new GalaxyService()); - container.service("messageService", () => new MessageService()); - container.service("planetService", () => new PlanetService()); - container.service("shipService", () => new ShipService()); - container.service("techService", () => new TechService()); - container.service("userService", () => new UserService()); - - return container; -}; diff --git a/src/ioc/inversify.config.ts b/src/ioc/inversify.config.ts new file mode 100644 index 0000000..8cbe6d2 --- /dev/null +++ b/src/ioc/inversify.config.ts @@ -0,0 +1,104 @@ +import { Controller } from "tsoa"; +import { Container, inject, decorate, injectable } from "inversify"; +import { autoProvide, buildProviderModule } from "inversify-binding-decorators"; + +import TYPES from "./types"; +import UserService from "../services/UserService"; +import IUserService from "../interfaces/services/IUserService"; +import BuildingService from "../services/BuildingService"; +import DefenseService from "../services/DefenseService"; +import EventService from "../services/EventService"; +import GalaxyService from "../services/GalaxyService"; +import MessageService from "../services/MessageService"; +import PlanetService from "../services/PlanetService"; +import ShipService from "../services/ShipService"; +import TechService from "../services/TechService"; +import AuthService from "../services/AuthService"; +import IBuildingService from "../interfaces/services/IBuildingService"; +import IDefenseService from "../interfaces/services/IDefenseService"; +import IEventService from "../interfaces/services/IEventService"; +import IGalaxyService from "../interfaces/services/IGalaxyService"; +import IMessageService from "../interfaces/services/IMessageService"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import IShipService from "../interfaces/services/IShipService"; +import ITechService from "../interfaces/services/ITechService"; +import IAuthService from "../interfaces/services/IAuthService"; +import { AuthRouter } from "../routes/AuthRouter"; +import ILogger from "../interfaces/ILogger"; +import SimpleLogger from "../loggers/SimpleLogger"; +import { UserRouter } from "../routes/UserRouter"; +import BuildingRepository from "../repositories/BuildingRepository"; +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import PlanetRepository from "../repositories/PlanetRepository"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import UserRepository from "../repositories/UserRepository"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; +import TechnologiesRepository from "../repositories/TechnologiesRepository"; +import ITechnologiesRepository from "../interfaces/repositories/ITechnologiesRepository"; +import RequirementsService from "../services/RequirementsService"; +import IRequirementsService from "../interfaces/services/IRequirementsService"; +import DefenseRepository from "../repositories/DefenseRepository"; +import IDefenseRepository from "../interfaces/repositories/IDefenseRepository"; +import MessageRepository from "../repositories/MessageRepository"; +import IMessageRepository from "../interfaces/repositories/IMessageRepository"; +import IShipsRepository from "../interfaces/repositories/IShipsRepository"; +import ShipsRepository from "../repositories/ShipsRepository"; +import GalaxyRepository from "../repositories/GalaxyRepository"; +import IGalaxyRepository from "../interfaces/repositories/IGalaxyRepository"; +import { BuildingsRouter } from "../routes/BuildingsRouter"; +import { ConfigRouter } from "../routes/ConfigRouter"; +import { DefenseRouter } from "../routes/DefenseRouter"; +import { GalaxyRouter } from "../routes/GalaxyRouter"; +import { MessagesRouter } from "../routes/MessagesRouter"; +import { PlanetsRouter } from "../routes/PlanetsRouter"; +import { ShipsRouter } from "../routes/ShipsRouter"; +import { TechsRouter } from "../routes/TechsRouter"; +import ErrorHandler from "../common/ErrorHandler"; +import IErrorHandler from "../interfaces/IErrorHandler"; + +const iocContainer = new Container(); + +decorate(injectable(), Controller); + +iocContainer.load(buildProviderModule()); + +// Services +iocContainer.bind(TYPES.ILogger).to(SimpleLogger); +iocContainer.bind(TYPES.IUserService).to(UserService); +iocContainer.bind(TYPES.IBuildingService).to(BuildingService); +iocContainer.bind(TYPES.IDefenseService).to(DefenseService); +iocContainer.bind(TYPES.IEventService).to(EventService); +iocContainer.bind(TYPES.IGalaxyService).to(GalaxyService); +iocContainer.bind(TYPES.IMessageService).to(MessageService); +iocContainer.bind(TYPES.IPlanetService).to(PlanetService); +iocContainer.bind(TYPES.IShipService).to(ShipService); +iocContainer.bind(TYPES.ITechService).to(TechService); +iocContainer.bind(TYPES.IAuthService).to(AuthService); +iocContainer.bind(TYPES.IRequirementsService).to(RequirementsService); + +// Repositories +iocContainer.bind(TYPES.IBuildingRepository).to(BuildingRepository); +iocContainer.bind(TYPES.IPlanetRepository).to(PlanetRepository); +iocContainer.bind(TYPES.IUserRepository).to(UserRepository); +iocContainer.bind(TYPES.ITechnologiesRepository).to(TechnologiesRepository); +iocContainer.bind(TYPES.IDefenseRepository).to(DefenseRepository); +iocContainer.bind(TYPES.IMessageRepository).to(MessageRepository); +iocContainer.bind(TYPES.IShipsRepository).to(ShipsRepository); +iocContainer.bind(TYPES.IGalaxyRepository).to(GalaxyRepository); + +// Routers +iocContainer.bind(TYPES.AuthRouter).to(AuthRouter); +iocContainer.bind(TYPES.BuildingsRouter).to(BuildingsRouter); +iocContainer.bind(TYPES.ConfigRouter).to(ConfigRouter); +iocContainer.bind(TYPES.DefenseRouter).to(DefenseRouter); +// iocContainer.bind(TYPES.EventRouter).to(EventRouter); +iocContainer.bind(TYPES.GalaxyRouter).to(GalaxyRouter); +iocContainer.bind(TYPES.MessagesRouter).to(MessagesRouter); +iocContainer.bind(TYPES.PlanetsRouter).to(PlanetsRouter); +iocContainer.bind(TYPES.ShipsRouter).to(ShipsRouter); +iocContainer.bind(TYPES.TechsRouter).to(TechsRouter); +iocContainer.bind(TYPES.UsersRouter).to(UserRouter); + +iocContainer.bind(TYPES.IErrorHandler).to(ErrorHandler); + +export { iocContainer, autoProvide, inject, decorate, injectable }; diff --git a/src/ioc/types.ts b/src/ioc/types.ts new file mode 100644 index 0000000..b482199 --- /dev/null +++ b/src/ioc/types.ts @@ -0,0 +1,44 @@ +import "reflect-metadata"; + +/* eslint-disable @typescript-eslint/naming-convention */ + +const TYPES = { + ILogger: Symbol("ILogger"), + + IUserService: Symbol("IUserService"), + IBuildingService: Symbol("IBuildingService"), + IDefenseService: Symbol("IDefenseService"), + IEventService: Symbol("IEventService"), + IGalaxyService: Symbol("IGalaxyService"), + IMessageService: Symbol("IMessageService"), + IPlanetService: Symbol("IPlanetService"), + IShipService: Symbol("IShipService"), + ITechService: Symbol("ITechService"), + IAuthService: Symbol("IAuthService"), + IRequirementsService: Symbol("IRequirementsService"), + + IBuildingRepository: Symbol("IBuildingRepository"), + IPlanetRepository: Symbol("IPlanetRepository"), + IUserRepository: Symbol("IUserRepository"), + ITechnologiesRepository: Symbol("ITechnologiesRepository"), + IDefenseRepository: Symbol("IDefenseRepository"), + IGalaxyRepository: Symbol("IGalaxyRepository"), + IMessageRepository: Symbol("IMessageRepository"), + IShipsRepository: Symbol("IShipsRepository"), + + AuthRouter: Symbol("AuthRouter"), + BuildingsRouter: Symbol("BuildingsRouter"), + ConfigRouter: Symbol("ConfigRouter"), + DefenseRouter: Symbol("DefenseRouter"), + EventRouter: Symbol("EventRouter"), + GalaxyRouter: Symbol("GalaxyRouter"), + MessagesRouter: Symbol("MessagesRouter"), + PlanetsRouter: Symbol("PlanetsRouter"), + ShipsRouter: Symbol("ShipsRouter"), + TechsRouter: Symbol("TechsRouter"), + UsersRouter: Symbol("UsersRouter"), + + IErrorHandler: Symbol("IErrorHandler"), +}; + +export default TYPES; diff --git a/src/loggers/SimpleLogger.ts b/src/loggers/SimpleLogger.ts index 6159d3a..bafc66f 100644 --- a/src/loggers/SimpleLogger.ts +++ b/src/loggers/SimpleLogger.ts @@ -1,41 +1,23 @@ import ILogger from "../interfaces/ILogger"; +import { injectable } from "inversify"; -/** - * This class represents a simple logger which logs - * straight to the console. - */ +@injectable() export default class SimpleLogger implements ILogger { - /** - * Log a message of severity 'error' - * @param message the message - */ public error(message: string) { // tslint:disable-next-line:no-console console.error(message); } - /** - * Log a message of severity 'warn' - * @param message the message - */ public warn(message: string) { // tslint:disable-next-line:no-console console.warn(message); } - /** - * Log a message of severity 'info' - * @param message the message - */ public info(message: string) { // tslint:disable-next-line:no-console console.info(message); } - /** - * Log a message of severity 'log' - * @param message the message - */ public log(message: string) { // tslint:disable-next-line:no-console console.log(message); diff --git a/src/middlewares/authentication.ts b/src/middlewares/authentication.ts new file mode 100644 index 0000000..6cbd10c --- /dev/null +++ b/src/middlewares/authentication.ts @@ -0,0 +1,35 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as express from "express"; +import * as jwt from "jsonwebtoken"; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export function expressAuthentication(request: express.Request, securityName: string, scopes?: string[]): Promise { + if (securityName === "JWT" || securityName === "jwt") { + let token: string = (request.headers["access-token"] as string) || (request.headers.authorization as string); + + if (token.startsWith("Bearer")) { + token = token.replace("Bearer ", ""); + } + + return new Promise((resolve, reject) => { + if (!token) { + return reject(new Error("No token provided")); + } + + jwt.verify(token, process.env.JWT_SECRET, function(err: any, decoded: any) { + if (err) { + return reject(err); + } + + // Check if JWT contains all required scopes + for (const scope of scopes) { + if (!decoded.scopes.includes(scope)) { + return reject(new Error("JWT does not contain required scope.")); + } + } + + return resolve(decoded); + }); + }); + } +} diff --git a/src/repositories/BuildingRepository.ts b/src/repositories/BuildingRepository.ts new file mode 100644 index 0000000..90238f7 --- /dev/null +++ b/src/repositories/BuildingRepository.ts @@ -0,0 +1,84 @@ +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import Buildings from "../units/Buildings"; +import Database from "../common/Database"; +import InputValidator from "../common/InputValidator"; +import SerializationHelper from "../common/SerializationHelper"; +import * as squel from "safe-squel"; +import { injectable } from "inversify"; + +@injectable() +export default class BuildingRepository implements IBuildingRepository { + public async create(t: Buildings): Promise { + const query = squel + .insert() + .into("buildings") + .set("planetID", t.planetID) + .toString(); + + await Database.query(query); + + return t; + } + + public async createTransactional(planetID: number, connection): Promise { + const query = squel + .insert() + .into("buildings") + .set("planetID", planetID) + .toString(); + + await connection.query(query); + } + + public async exists(id: number): Promise { + const query: string = squel + .select() + .from("buildings", "b") + .where("b.planetID = ?", id) + .toString(); + + const [rows] = await Database.query(query); + + return InputValidator.isSet(rows); + } + + public async getById(id: number): Promise { + const query: string = squel + .select() + .from("buildings", "b") + .where("b.planetID = ?", id) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return SerializationHelper.toInstance(new Buildings(), JSON.stringify(rows[0])); + } + + public async save(t: Buildings): Promise { + const query: string = squel + .update() + .table("buildings", "b") + .set("metalMine", t.metalMine) + .set("crystalMine", t.crystalMine) + .set("deuteriumSynthesizer", t.deuteriumSynthesizer) + .set("solarPlant", t.solarPlant) + .set("fusionReactor", t.fusionReactor) + .set("roboticFactory", t.roboticFactory) + .set("naniteFactory", t.naniteFactory) + .set("shipyard", t.shipyard) + .set("metalStorage", t.metalMine) + .set("crystalStorage", t.crystalStorage) + .set("deuteriumStorage", t.deuteriumStorage) + .set("researchLab", t.researchLab) + .set("terraformer", t.terraformer) + .set("allianceDepot", t.allianceDepot) + .where("planetID = ?", t.planetID) + .toString(); + + await Database.query(query); + } +} diff --git a/src/repositories/DefenseRepository.ts b/src/repositories/DefenseRepository.ts new file mode 100644 index 0000000..fc4ee90 --- /dev/null +++ b/src/repositories/DefenseRepository.ts @@ -0,0 +1,86 @@ +import IDefenseRepository from "../interfaces/repositories/IDefenseRepository"; +import Defenses from "../units/Defenses"; +import * as squel from "safe-squel"; +import Database from "../common/Database"; +import InputValidator from "../common/InputValidator"; +import { injectable } from "inversify"; +import SerializationHelper from "../common/SerializationHelper"; + +@injectable() +export default class DefenseRepository implements IDefenseRepository { + public async exists(id: number): Promise { + const query: string = squel + .select() + .field("p.ownerID", "ownerID") + .field("d.*") + .from("defenses", "d") + .left_join("planets", "p", "d.planetID = p.planetID") + .where("d.planetID = ?", id) + .toString(); + + const [[rows]] = await Database.query(query); + + return InputValidator.isSet(rows); + } + + public async getById(id: number): Promise { + const query: string = squel + .select() + .field("p.ownerID", "ownerID") + .field("d.*") + .from("defenses", "d") + .left_join("planets", "p", "d.planetID = p.planetID") + .where("d.planetID = ?", id) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return SerializationHelper.toInstance(new Defenses(), JSON.stringify(rows[0])); + } + + public async save(t: Defenses): Promise { + const query: string = squel + .update() + .table("defenses", "d") + .set("rocketLauncher", t.rocketLauncher) + .set("lightLaser", t.lightLaser) + .set("heavyLaser", t.heavyLaser) + .set("ionCannon", t.ionCannon) + .set("gaussCannon", t.gaussCannon) + .set("plasmaTurret", t.plasmaTurret) + .set("smallShieldDome", t.smallShieldDome) + .set("largeShieldDome", t.largeShieldDome) + .set("antiBallisticMissile", t.antiBallisticMissile) + .set("interplanetaryMissile", t.interplanetaryMissile) + .where("planetID = ?", t.planetID) + .toString(); + + await Database.query(query); + } + + public async create(t: Defenses): Promise { + const query = squel + .insert() + .into("defenses") + .set("planetID", t.planetID) + .toString(); + + await Database.query(query); + + return t; + } + + public async createTransactional(planetID: number, connection): Promise { + const query = squel + .insert() + .into("defenses") + .set("planetID", planetID) + .toString(); + + return await connection.query(query); + } +} diff --git a/src/repositories/GalaxyRepository.ts b/src/repositories/GalaxyRepository.ts new file mode 100644 index 0000000..1519b13 --- /dev/null +++ b/src/repositories/GalaxyRepository.ts @@ -0,0 +1,71 @@ +import IGalaxyRepository from "../interfaces/repositories/IGalaxyRepository"; +import Database from "../common/Database"; +import * as squel from "safe-squel"; +import GalaxyPositionInfo from "../units/GalaxyPositionInfo"; +import { injectable } from "inversify"; +import { Globals } from "../common/Globals"; +import PlanetType = Globals.PlanetType; +import ICoordinates from "../interfaces/ICoordinates"; +import GalaxyRow from "../units/GalaxyRow"; + +@injectable() +export default class GalaxyRepository implements IGalaxyRepository { + public async getPositionInfo(posGalaxy: number, posSystem: number): Promise { + const query: string = squel + .select() + .field("p.planetID") + .field("p.ownerID") + .field("u.username") + .field("p.name", "planetName") + .field("p.posGalaxy") + .field("p.posSystem") + .field("p.posPlanet") + .field("p.lastUpdate") + .field("p.planetType") + .field("p.image") + .field("g.debrisMetal") + .field("g.debrisCrystal") + .field("p.destroyed") + .from("galaxy", "g") + .left_join("planets", "p", "g.planetID = p.planetID") + .left_join("users", "u", "u.userID = p.ownerID") + .where("p.posGalaxy = ?", posGalaxy) + .where("p.posSystem = ?", posSystem) + .toString(); + + const [rows] = await Database.query(query); + + return rows; + } + + public async getFreePosition( + maxGalaxy: number, + maxSystem: number, + minPlanetPos: number, + maxPlanetPos: number, + ): Promise { + const queryUser = `CALL getFreePosition(${maxGalaxy}, ${maxSystem}, ${minPlanetPos}, ${maxPlanetPos});`; + + const [[[result]]] = await Database.query(queryUser); + + return { + posGalaxy: result.posGalaxy, + posSystem: result.posSystem, + posPlanet: result.posPlanet, + type: PlanetType.PLANET, + }; + } + + public async createTransactional(row: GalaxyRow, connection): Promise { + const query = squel + .insert() + .into("galaxy") + .set("planetID", row.planetID) + .set("posGalaxy", row.posGalaxy) + .set("posSystem", row.posSystem) + .set("posPlanet", row.posPlanet) + .toString(); + + return await connection.query(query); + } +} diff --git a/src/repositories/MessageRepository.ts b/src/repositories/MessageRepository.ts new file mode 100644 index 0000000..e3cc918 --- /dev/null +++ b/src/repositories/MessageRepository.ts @@ -0,0 +1,124 @@ +import IMessageRepository from "../interfaces/repositories/IMessageRepository"; +import Message from "../units/Message"; +import Database from "../common/Database"; +import InputValidator from "../common/InputValidator"; +import SerializationHelper from "../common/SerializationHelper"; +import * as squel from "safe-squel"; +import { injectable } from "inversify"; + +@injectable() +export default class MessageRepository implements IMessageRepository { + public async exists(messageID: number): Promise { + const query: string = squel + .select() + .from("messages") + .field("messageID") + .field("senderID") + .field("receiverID") + .field("sendtime") + .field("type") + .field("subject") + .field("body") + .where("messageID = ?", messageID) + .where("deleted = ?", 0) + .toString(); + + const [rows] = await Database.query(query.toString()); + + return InputValidator.isSet(rows); + } + + public async getById(messageID: number): Promise { + const query: string = squel + .select() + .from("messages") + .where("messageID = ?", messageID) + .where("deleted = ?", 0) + .toString(); + + const [rows] = await Database.query(query.toString()); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return SerializationHelper.toInstance(new Message(), JSON.stringify(rows[0])); + } + + public async save(t: Message): Promise { + const query: string = squel + .update() + .table("messages", "m") + .set("senderID", t.senderID) + .set("receiverID", t.receiverID) + .set("sendtime", t.sendtime) + .set("type", t.type) + .set("subject", t.subject) + .set("body", t.body) + .set("deleted", t.deleted) + .toString(); + + await Database.query(query); + } + + public async create(t: Message): Promise { + const query: string = squel + .insert() + .into("messages") + .set("senderID", t.senderID) + .set("receiverID", t.receiverID) + .set( + "sendtime", + new Date() + .toISOString() + .slice(0, 19) + .replace("T", " "), + ) + .set("type", t.type) + .set("subject", t.subject) + .set("body", t.body) + .set("deleted", false) + .toString(); + + await Database.query(query); + // TODO: set messageID + return t; + } + + public async remove(userID: number, messageID: number): Promise { + const query: string = squel + .update() + .table("messages") + .set("deleted", 1) + .where("messageID = ?", messageID) + .where("receiverID = ?", userID) + .toString(); + + await Database.query(query); + } + + public async getAll(userID: number): Promise { + const query: string = squel + .select() + .from("messages") + .field("messageID") + .field("senderID") + .field("receiverID") + .field("sendtime") + .field("type") + .field("subject") + .field("body") + .where("receiverID = ?", userID) + .where("deleted = ?", 0) + .order("sendtime", false) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return rows; + } +} diff --git a/src/repositories/PlanetRepository.spec.ts b/src/repositories/PlanetRepository.spec.ts new file mode 100644 index 0000000..cc7a7c6 --- /dev/null +++ b/src/repositories/PlanetRepository.spec.ts @@ -0,0 +1,35 @@ +import * as chai from "chai"; +import Planet from "../units/Planet"; +import { iocContainer } from "../ioc/inversify.config"; +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; + +const expect = chai.expect; + +const planetRepository = iocContainer.get(TYPES.IPlanetRepository); + +describe("PlanetService", () => { + it("should return a planet", async () => { + const ownerID = 1; + const planetID = 167546850; + + const result = await planetRepository.getById(planetID); + + expect(result.ownerID).to.be.equals(ownerID); + expect(result.planetID).to.be.equals(planetID); + }); + + it("should update a planet", async () => { + const newName = "SomethingElse"; + + const planet: Planet = await planetRepository.getById(167546850); + + planet.name = newName; + + await planetRepository.save(planet); + + const result = await planetRepository.getById(planet.planetID); + + expect(result.name).to.be.equals(newName); + }); +}); diff --git a/src/repositories/PlanetRepository.ts b/src/repositories/PlanetRepository.ts new file mode 100644 index 0000000..dba964d --- /dev/null +++ b/src/repositories/PlanetRepository.ts @@ -0,0 +1,206 @@ +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import Planet from "../units/Planet"; +import { injectable } from "inversify"; +import Database from "../common/Database"; +import InputValidator from "../common/InputValidator"; +import * as squel from "safe-squel"; +import Event from "../units/Event"; +import SerializationHelper from "../common/SerializationHelper"; + +@injectable() +export default class PlanetRepository implements IPlanetRepository { + public async exists(id: number): Promise { + const query = squel + .select() + .from("planets") + .where("planetID = ?", id) + .toString(); + + const [result] = await Database.query(query); + + return InputValidator.isSet(result); + } + + public async getById(id: number): Promise { + const query = squel + .select() + .from("planets") + .where("planetID = ?", id) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return SerializationHelper.toInstance(new Planet(), JSON.stringify(rows[0])); + } + + public async save(t: Planet): Promise { + const query = squel + .update() + .table("planets") + .set("ownerID", t.ownerID) + .set("name", t.name) + .set("posGalaxy", t.posGalaxy) + .set("posSystem", t.posSystem) + .set("posPlanet", t.posPlanet) + .set("lastUpdate", t.lastUpdate) + .set("planetType", t.planetType) + .set("image", t.image) + .set("diameter", t.diameter) + .set("fieldsCurrent", t.fieldsCurrent) + .set("fieldsMax", t.fieldsMax) + .set("tempMin", t.tempMin) + .set("tempMax", t.tempMax) + .set("metal", t.metal) + .set("crystal", t.crystal) + .set("deuterium", t.deuterium) + .set("energyUsed", t.energyUsed) + .set("energyMax", t.energyMax) + .set("metalMinePercent", t.metalMinePercent) + .set("crystalMinePercent", t.crystalMinePercent) + .set("deuteriumSynthesizerPercent", t.deuteriumSynthesizerPercent) + .set("solarPlantPercent", t.solarPlantPercent) + .set("fusionReactorPercent", t.fusionReactorPercent) + .set("solarSatellitePercent", t.solarSatellitePercent) + .set("bBuildingId", t.bBuildingId) + .set("bBuildingEndTime", t.bBuildingEndTime) + .set("bBuildingDemolition", t.bBuildingDemolition) + .set("bHangarQueue", t.bHangarQueue) + .set("bHangarStartTime", t.bHangarStartTime) + .set("bHangarPlus", t.bHangarPlus) + .set("destroyed", t.destroyed) + .where("planetID = ?", t.planetID) + .toString(); + + await Database.query(query); + } + + public async create(t: Planet): Promise { + const query = squel + .insert() + .into("planets") + .set("ownerID", t.ownerID) + .set("name", t.name) + .set("posGalaxy", t.posGalaxy) + .set("posSystem", t.posSystem) + .set("posPlanet", t.posPlanet) + .set("lastUpdate", t.lastUpdate) + .set("planetType", t.planetType) + .set("image", t.image) + .set("diameter", t.diameter) + .set("fieldsCurrent", t.fieldsCurrent) + .set("fieldsMax", t.fieldsMax) + .set("tempMin", t.tempMin) + .set("tempMax", t.tempMax) + .set("metal", t.metal) + .set("crystal", t.crystal) + .set("deuterium", t.deuterium) + .set("energyUsed", t.energyUsed) + .set("energyMax", t.energyMax) + .set("metalMinePercent", t.metalMinePercent) + .set("crystalMinePercent", t.crystalMinePercent) + .set("deuteriumSynthesizerPercent", t.deuteriumSynthesizerPercent) + .set("solarPlantPercent", t.solarPlantPercent) + .set("fusionReactorPercent", t.fusionReactorPercent) + .set("solarSatellitePercent", t.solarSatellitePercent) + .set("bBuildingId", t.bBuildingId) + .set("bBuildingEndTime", t.bBuildingEndTime) + .set("bBuildingDemolition", t.bBuildingDemolition) + .set("bHangarQueue", t.bHangarQueue) + .set("bHangarStartTime", t.bHangarStartTime) + .set("bHangarPlus", t.bHangarPlus) + .set("destroyed", t.destroyed); + + if (InputValidator.isSet(t.planetID)) { + query.set("planetID", t.planetID); + } + + await Database.query(query.toString()); + + return t; + } + + public async createTransactional(t: Planet, connection): Promise { + const query = squel + .insert() + .into("planets") + .set("planetID", t.planetID) + .set("ownerID", t.ownerID) + .set("name", t.name) + .set("posGalaxy", t.posGalaxy) + .set("posSystem", t.posSystem) + .set("posPlanet", t.posPlanet) + .set("lastUpdate", t.lastUpdate) + .set("planetType", t.planetType) + .set("image", t.image) + .set("diameter", t.diameter) + .set("fieldsMax", t.fieldsMax) + .set("tempMin", t.tempMin) + .set("tempMax", t.tempMax) + .set("metal", t.metal) + .set("crystal", t.crystal) + .set("deuterium", t.deuterium) + .toString(); + + return await connection.query(query); + } + + public async getAllOfUser(userID): Promise { + const query = squel + .select() + .from("planets") + .where("ownerID = ?", userID); + + const [rows] = await Database.query(query.toString()); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return rows; + } + + public async getMovement(userID: number, planetID: number): Promise { + const query: string = squel + .select() + .from("events") + .where("ownerID = ?", userID) + .where( + squel + .expr() + .or(`startID = ${planetID}`) + .or(`endID = ${planetID}`), + ) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return rows; + } + + public async delete(planetID: number, userID: number): Promise { + const query: string = squel + .delete() + .from("planets") + .where("planetID = ?", planetID) + .where("ownerID = ?", userID) + .toString(); + + return await Database.query(query); + } + + public async getNewId(): Promise { + const query = "CALL getNewPlanetId();"; + + const [[[result]]] = await Database.query(query); + + return result.planetID; + } +} diff --git a/src/repositories/ShipsRepository.ts b/src/repositories/ShipsRepository.ts new file mode 100644 index 0000000..344e97a --- /dev/null +++ b/src/repositories/ShipsRepository.ts @@ -0,0 +1,103 @@ +import IShipsRepository from "../interfaces/repositories/IShipsRepository"; +import Ships from "../units/Ships"; +import Database from "../common/Database"; +import InputValidator from "../common/InputValidator"; +import * as squel from "safe-squel"; + +import { injectable } from "inversify"; +import SerializationHelper from "../common/SerializationHelper"; + +@injectable() +export default class ShipsRepository implements IShipsRepository { + public async create(t: Ships): Promise { + const query = squel + .insert() + .into("ships") + .set("planetID", t.planetID) + .toString(); + + await Database.query(query); + + return t; + } + + public async exists(id: number): Promise { + const query = squel + .select() + .from("ships") + .where("planetID = ?", id) + .toString(); + + return InputValidator.isSet(await Database.query(query)); + } + + public async getById(id: number): Promise { + const query = squel + .select() + .from("ships") + .where("planetID = ?", id) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return SerializationHelper.toInstance(new Ships(), JSON.stringify(rows[0])); + } + + public async save(t: Ships): Promise { + const query = squel + .update() + .table("planets") + .set("smallCargoShip", t.smallCargoShip) + .set("largeCargoShip", t.largeCargoShip) + .set("lightFighter", t.lightFighter) + .set("heavyFighter", t.heavyFighter) + .set("cruiser", t.cruiser) + .set("battleship", t.battleship) + .set("colonyShip", t.colonyShip) + .set("recycler", t.recycler) + .set("espionageProbe", t.espionageProbe) + .set("bomber", t.bomber) + .set("solarSatellite", t.solarSatellite) + .set("destroyer", t.destroyer) + .set("battlecruiser", t.battlecruiser) + .set("deathstar", t.deathstar) + .where("planetID = ?", t.planetID) + .toString(); + + await Database.query(query); + } + + public async getShips(planetID: number, userID: number): Promise { + const query: string = squel + .select() + .field("p.ownerID") + .field("s.*") + .from("ships", "s") + .left_join("planets", "p", "s.planetID = p.planetID") + .where("s.planetID = ?", planetID) + .where("p.ownerID = ?", userID) + .toString(); + + const [[rows]] = await Database.query(query.toString()); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return rows; + } + + public async createTransactional(planetID: number, connection): Promise { + const query = squel + .insert() + .into("ships") + .set("planetID", planetID) + .toString(); + + return await connection.query(query); + } +} diff --git a/src/repositories/TechRepository.spec.ts b/src/repositories/TechRepository.spec.ts new file mode 100644 index 0000000..543e265 --- /dev/null +++ b/src/repositories/TechRepository.spec.ts @@ -0,0 +1,22 @@ +import * as chai from "chai"; +import { iocContainer } from "../ioc/inversify.config"; + +import TYPES from "../ioc/types"; +import ITechnologiesRepository from "../interfaces/repositories/ITechnologiesRepository"; +import Techs from "../units/Techs"; + +const technologiesRepository = iocContainer.get(TYPES.ITechnologiesRepository); + +const expect = chai.expect; + +describe("TechService", () => { + it("should fail (duplicate entry)", async () => { + try { + await technologiesRepository.create({ + userID: 1, + } as Techs); + } catch (error) { + expect(error.message).contains("Duplicate entry"); + } + }); +}); diff --git a/src/repositories/TechnologiesRepository.ts b/src/repositories/TechnologiesRepository.ts new file mode 100644 index 0000000..3390713 --- /dev/null +++ b/src/repositories/TechnologiesRepository.ts @@ -0,0 +1,85 @@ +import ITechnologiesRepository from "../interfaces/repositories/ITechnologiesRepository"; +import Techs from "../units/Techs"; +import { injectable } from "inversify"; +import * as squel from "safe-squel"; +import Database from "../common/Database"; +import InputValidator from "../common/InputValidator"; +import SerializationHelper from "../common/SerializationHelper"; + +@injectable() +export default class TechnologiesRepository implements ITechnologiesRepository { + public async exists(id: number): Promise { + const query: string = squel + .select() + .from("techs") + .where("userID = ?", id) + .toString(); + + const [[rows]] = await Database.query(query); + + return InputValidator.isSet(rows); + } + + public async getById(id: number): Promise { + const query: string = squel + .select() + .from("techs") + .where("userID = ?", id) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + return SerializationHelper.toInstance(new Techs(), JSON.stringify(rows[0])); + } + + public async save(t: Techs): Promise { + const query = squel + .update() + .table("techs") + .set("espionageTech", t.espionageTech) + .set("computerTech", t.computerTech) + .set("weaponTech", t.weaponTech) + .set("armourTech", t.armourTech) + .set("shieldingTech", t.shieldingTech) + .set("energyTech", t.energyTech) + .set("hyperspaceTech", t.hyperspaceTech) + .set("combustionDriveTech", t.combustionDriveTech) + .set("impulseDriveTech", t.impulseDriveTech) + .set("hyperspaceDriveTech", t.hyperspaceDriveTech) + .set("laserTech", t.laserTech) + .set("ionTech", t.ionTech) + .set("plasmaTech", t.plasmaTech) + .set("intergalacticResearchTech", t.intergalacticResearchTech) + .set("gravitonTech", t.gravitonTech) + .where("userID = ?", t.userID) + .toString(); + + await Database.query(query); + } + + public async create(t: Techs): Promise { + const query = squel + .insert() + .into("techs") + .set("userID", t.userID) + .toString(); + + await Database.query(query); + + return t; + } + + public async createTransactional(userID: number, connection): Promise { + const query = squel + .insert() + .into("techs") + .set("userID", userID) + .toString(); + + return await connection.query(query); + } +} diff --git a/src/services/UserService.spec.ts b/src/repositories/UserRepository.spec.ts similarity index 56% rename from src/services/UserService.spec.ts rename to src/repositories/UserRepository.spec.ts index 5648cf7..a3aef5c 100644 --- a/src/services/UserService.spec.ts +++ b/src/repositories/UserRepository.spec.ts @@ -1,15 +1,16 @@ import * as chai from "chai"; +import { iocContainer } from "../ioc/inversify.config"; -const expect = chai.expect; +import TYPES from "../ioc/types"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); +const expect = chai.expect; -const container = createContainer(); +const userRepository = iocContainer.get(TYPES.IUserRepository); describe("UserService", () => { it("should return a valid userID", async () => { - const result = await container.userService.getNewId(); + const result = await userRepository.getNewId(); expect(result).to.be.above(0); }); @@ -17,7 +18,7 @@ describe("UserService", () => { it("should return information about an authenticated user", async () => { const userID = 1; - const result = await container.userService.getAuthenticatedUser(userID); + const result = await userRepository.getById(userID); expect(result.userID).to.be.equals(userID); expect(result.username).to.be.equals("admin"); @@ -29,19 +30,16 @@ describe("UserService", () => { it("should return information about an user", async () => { const userID = 1; - const result = await container.userService.getUserById(userID); + const result = await userRepository.getById(userID); expect(result.userID).to.be.equals(userID); expect(result.username).to.be.equals("admin"); - expect(result.email).to.be.equals(undefined); - expect(result.lastTimeOnline).to.be.equals(undefined); - expect(result.currentPlanet).to.be.equals(undefined); }); it("should return nothing because the user does not exist", async () => { const userID = -1; - const result = await container.userService.getUserById(userID); + const result = await userRepository.getById(userID); expect(result).to.be.equals(null); }); @@ -50,7 +48,7 @@ describe("UserService", () => { const email = "user_1501005189510@test.com"; const userID = 1; - const result = await container.userService.getUserForAuthentication(email); + const result = await userRepository.getById(userID); expect(result.userID).to.be.equals(userID); expect(result.email).to.be.equals(email); @@ -58,9 +56,9 @@ describe("UserService", () => { }); it("should return nothing because the user does not exist", async () => { - const email = "idontexist@test.com"; + const userID = -1; - const result = await container.userService.getUserForAuthentication(email); + const result = await userRepository.getById(userID); expect(result).to.be.equals(null); }); @@ -68,28 +66,24 @@ describe("UserService", () => { it("should return nothing because the user does not exist", async () => { const userID = -1; - const result = await container.userService.getUserById(userID); + const result = await userRepository.getById(userID); expect(result).to.be.equals(null); }); it("should return username taken and email taken", async () => { - const username = "admin"; const email = "user_1501005189510@test.com"; - const result = await container.userService.checkIfNameOrMailIsTaken(username, email); + const result = await userRepository.checkEmailTaken(email); - expect(result.username_taken).to.be.equals(1); - expect(result.email_taken).to.be.equals(1); + expect(result).to.be.equals(true); }); it("should return username free and email free", async () => { const username = "WhatEver"; - const email = "whatever@test.com"; - const result = await container.userService.checkIfNameOrMailIsTaken(username, email); + const result = await userRepository.checkUsernameTaken(username); - expect(result.username_taken).to.be.equals(0); - expect(result.email_taken).to.be.equals(0); + expect(result).to.be.equals(false); }); }); diff --git a/src/repositories/UserRepository.ts b/src/repositories/UserRepository.ts new file mode 100644 index 0000000..6ef1d9e --- /dev/null +++ b/src/repositories/UserRepository.ts @@ -0,0 +1,141 @@ +import IUserRepository from "../interfaces/repositories/IUserRepository"; +import User from "../units/User"; +import { injectable } from "inversify"; +import Database from "../common/Database"; +import InputValidator from "../common/InputValidator"; +import * as squel from "safe-squel"; +import SerializationHelper from "../common/SerializationHelper"; + +@injectable() +export default class UserRepository implements IUserRepository { + public async getUserForAuthentication(email: string): Promise { + const query: string = squel + .select() + .from("users") + .where("email = ?", email) + .toString(); + + const [[result]] = await Database.query(query); + + if (!InputValidator.isSet(result)) { + return null; + } + + return SerializationHelper.toInstance(new User(), JSON.stringify(result)); + } + + public async exists(id: number): Promise { + const query: string = squel + .select() + .from("users") + .where("userID = ?", id) + .toString(); + + return InputValidator.isSet(await Database.query(query)); + } + + public async getById(id: number): Promise { + const query: string = squel + .select() + .from("users") + .where("userID = ?", id) + .toString(); + + const [rows] = await Database.query(query); + + if (!InputValidator.isSet(rows)) { + return null; + } + + const user = SerializationHelper.toInstance(new User(), JSON.stringify(rows[0])); + + delete user.password; + + return user; + } + + public async save(t: User): Promise { + const query = squel.update().table("users"); + + if (InputValidator.isSet(t.username)) { + query.set("username", t.username); + } + if (InputValidator.isSet(t.password)) { + query.set("password", t.password); + } + if (InputValidator.isSet(t.email)) { + query.set("email", t.email); + } + if (InputValidator.isSet(t.lastTimeOnline)) { + query.set("lastTimeOnline", t.lastTimeOnline); + } + if (InputValidator.isSet(t.currentPlanet)) { + query.set("currentPlanet", t.currentPlanet); + } + if (InputValidator.isSet(t.bTechID)) { + query.set("bTechID", t.bTechID); + } + if (InputValidator.isSet(t.bTechEndTime)) { + query.set("bTechEndTime", t.bTechEndTime); + } + + query.where("userID = ?", t.userID); + + await Database.query(query.toString()); + } + + public async create(t: User): Promise { + const query: string = squel + .insert({ autoQuoteFieldNames: true }) + .into("users") + .set("userID", t.userID) + .set("username", t.username) + .set("password", t.password) + .set("email", t.email) + .set("lastTimeOnline", t.lastTimeOnline) + .set("currentPlanet", t.currentPlanet) + .toString(); + + await Database.query(query); + + return t; + } + + public async createTransactional(t: User, connection): Promise { + const query: string = squel + .insert({ autoQuoteFieldNames: true }) + .into("users") + .set("userID", t.userID) + .set("username", t.username) + .set("password", t.password) + .set("email", t.email) + .set("lastTimeOnline", t.lastTimeOnline) + .set("currentPlanet", t.currentPlanet) + .toString(); + return await connection.query(query); + } + + public async checkUsernameTaken(username: string): Promise { + const query = `SELECT EXISTS (SELECT 1 FROM users WHERE username LIKE '${username}') as result`; + + const [[data]] = await Database.query(query); + + return data.result === 1; + } + + public async checkEmailTaken(email: string): Promise { + const query = `SELECT EXISTS (SELECT 1 FROM users WHERE email LIKE '${email}') as result`; + + const [[data]] = await Database.query(query); + + return data.result === 1; + } + + public async getNewId(): Promise { + const queryUser = "CALL getNewUserId();"; + + const [[[result]]] = await Database.query(queryUser); + + return result.userID; + } +} diff --git a/src/routes/AuthRouter.spec.ts b/src/routes/AuthRouter.spec.ts index 1ebe55b..8d610ed 100644 --- a/src/routes/AuthRouter.spec.ts +++ b/src/routes/AuthRouter.spec.ts @@ -3,14 +3,8 @@ import chaiHttp = require("chai-http"); import App from "../App"; import { Globals } from "../common/Globals"; -import SimpleLogger from "../loggers/SimpleLogger"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); - -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -26,58 +20,60 @@ describe("authRoute", () => { it("should return a token", async () => { return await request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); expect(res.body.token).to.not.be.equals(null); }); }); it("authentication should fail (user does not exist)", async () => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "idonotexist@test.com", password: "idontexisteither" }) .then(res => { expect(res.body.error).equals("Authentication failed"); - expect(res.status).to.equals(Globals.Statuscode.NOT_AUTHORIZED); + expect(res.status).to.equals(Globals.StatusCodes.NOT_AUTHORIZED); }); }); it("authentication should fail (no password sent)", async () => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com" }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("authentication should fail (no email sent)", async () => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ password: "admin" }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("authentication should fail (nothing sent)", async () => { - return request.post("/v1/auth/login").then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + return request.post("/v1/login").then(res => { + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("authentication should fail (wrong password)", async () => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "iAmWrong" }) .then(res => { - expect(res.body.error).equals("Authentication failed"); - expect(res.status).to.equals(Globals.Statuscode.NOT_AUTHORIZED); + expect(res.status).to.equals(Globals.StatusCodes.NOT_AUTHORIZED); }); }); + + it("authentication should fail (no data sent)", async () => { + return request.post("/v1/login").then(res => { + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); + }); + }); }); diff --git a/src/routes/AuthRouter.ts b/src/routes/AuthRouter.ts index 4c6600d..b6083c0 100644 --- a/src/routes/AuthRouter.ts +++ b/src/routes/AuthRouter.ts @@ -1,76 +1,48 @@ -import { Response, Router } from "express"; -import { Globals } from "../common/Globals"; -import Encryption from "../common/Encryption"; import InputValidator from "../common/InputValidator"; -import JwtHelper from "../common/JwtHelper"; -import IRequest from "../interfaces/IRequest"; -import IUserService from "../interfaces/IUserService"; -import ILogger from "../interfaces/ILogger"; - -/** - * Defines routes for authentication - */ -export default class AuthRouter { - public router: Router = Router(); - private userService: IUserService; - private logger: ILogger; +import IUserService from "../interfaces/services/IUserService"; - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger - */ - public constructor(container, logger: ILogger) { - this.userService = container.userService; - this.router.post("/login", this.authenticate); - this.logger = logger; - } +import { Route, Post, Body, Tags, Controller, Res, TsoaResponse } from "tsoa"; - /** - * Validates the passed login-data. If the data is valid, - * a new JWT-token is returned. - * @param req - * @param response - * @param next - */ - public authenticate = async (req: IRequest, response: Response) => { +import { inject } from "inversify"; +import TYPES from "../ioc/types"; +import { provide } from "inversify-binding-decorators"; +import { Globals } from "../common/Globals"; +import AuthSuccessResponse from "../entities/responses/AuthSuccessResponse"; +import FailureResponse from "../entities/responses/FailureResponse"; +import AuthRequest from "../entities/requests/AuthRequest"; +import IAuthService from "../interfaces/services/IAuthService"; +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("login") +@Tags("Authentication") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(AuthRouter) +export class AuthRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IUserService) private userService: IUserService; + @inject(TYPES.IAuthService) private authService: IAuthService; + + @Post("/") + public async login( + @Body() req: AuthRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(req.body.email) || !InputValidator.isSet(req.body.password)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const email: string = InputValidator.sanitizeString(req.body.email); - - const password: string = InputValidator.sanitizeString(req.body.password); + const email: string = InputValidator.sanitizeString(req.email); + const password: string = InputValidator.sanitizeString(req.password); - const data = await this.userService.getUserForAuthentication(email); + const token = await this.authService.authenticateUser(email, password); - if (!InputValidator.isSet(data)) { - return response.status(Globals.Statuscode.NOT_AUTHORIZED).json({ - error: "Authentication failed", - }); - } - - const isValidPassword = await Encryption.compare(password, data.password); - - if (!isValidPassword) { - return response.status(Globals.Statuscode.NOT_AUTHORIZED).json({ - error: "Authentication failed", - }); - } - - return response.status(Globals.Statuscode.SUCCESS).json({ - token: JwtHelper.generateToken(data.userID), + return successResponse(Globals.StatusCodes.SUCCESS, { + token: token, }); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/BuildingsRouter.spec.ts b/src/routes/BuildingsRouter.spec.ts index aa342bc..550edd6 100644 --- a/src/routes/BuildingsRouter.spec.ts +++ b/src/routes/BuildingsRouter.spec.ts @@ -5,14 +5,15 @@ import App from "../App"; import { Globals } from "../common/Globals"; import Planet from "../units/Planet"; import User from "../units/User"; -import SimpleLogger from "../loggers/SimpleLogger"; +import { iocContainer } from "../ioc/inversify.config"; +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); +const planetRepository = iocContainer.get(TYPES.IPlanetRepository); +const userRepository = iocContainer.get(TYPES.IUserRepository); -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -24,9 +25,9 @@ describe("buildingsRoute", () => { let planetBeforeTests: Planet; before(async () => { - planetBeforeTests = await container.planetService.getPlanet(1, 167546850, true); + planetBeforeTests = await planetRepository.getById(167546850); return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -34,7 +35,7 @@ describe("buildingsRoute", () => { }); after(async () => { - await container.planetService.updatePlanet(planetBeforeTests); + await planetRepository.save(planetBeforeTests); }); beforeEach(function() { @@ -51,17 +52,16 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .then(res => { expect(res.body.planetID).equals(planetID); - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); }); }); - it("should return an empty list", () => { + it("should return nothing (player does not own the planet)", () => { return request .get("/v1/buildings/1") .set("Authorization", authToken) .then(res => { - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); - expect(res.body).to.be.empty; + expect(res.status).to.equals(Globals.StatusCodes.NOT_AUTHORIZED); }); }); @@ -70,8 +70,7 @@ describe("buildingsRoute", () => { .get("/v1/buildings/test") .set("Authorization", authToken) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); }); @@ -85,8 +84,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -96,8 +94,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ buildingID: 1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -109,8 +106,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID, buildingID: -1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -122,8 +118,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID, buildingID: 100 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -133,8 +128,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID: 1234, buildingID: 1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -148,7 +142,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID, buildingID: 1 }) .then(res => { - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); expect(res.body.planetID).to.be.equals(planetID); // TODO }); @@ -163,7 +157,7 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: "1" }) .then(res => { expect(res.body.error).equals("Planet already has a build-job"); - expect(res.status).equals(Globals.Statuscode.SUCCESS); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("cancel the build-order", () => { @@ -175,7 +169,7 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: "1" }) .then(res => { // TODO - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); expect(res.body.planetID).to.be.equals(planetID); }); }); @@ -188,7 +182,7 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: "1" }) .then(res => { expect(res.body.error).equals("Planet has no build-job"); - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); }); @@ -201,8 +195,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -212,8 +205,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ buildingID: 1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -225,8 +217,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID, buildingID: -1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -238,8 +229,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID, buildingID: 100 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -249,8 +239,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID: 1234, buildingID: 1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -262,8 +251,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID, buildingID: 1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -278,7 +266,7 @@ describe("buildingsRoute", () => { .then(res => { expect(res.body.planetID).equals(planetID); expect(res.body.bBuildingId).equals(buildingID); - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); }); }); @@ -291,7 +279,7 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: "1" }) .then(res => { expect(res.body.error).equals("Planet already has a build-job"); - expect(res.status).equals(Globals.Statuscode.SUCCESS); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -303,8 +291,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID: `${planetID}`, buildingID: "1" }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -320,7 +307,7 @@ describe("buildingsRoute", () => { expect(res.body.planetID).to.be.equals(planetID); expect(res.body.bBuildingId).to.be.equals(0); expect(res.body.bBuildingEndTime).to.be.equals(0); - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); }); }); @@ -333,7 +320,7 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: "1" }) .then(res => { expect(res.body.error).equals("Planet has no build-job"); - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -345,22 +332,21 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID: `${planetID}`, buildingID: "1" }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.NOT_AUTHORIZED); }); }); it("should fail (can't build shipyard/robotic/nanite while it is being used)", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planet: Planet = await planetRepository.getById(planetID); const valueBefore = planet.bHangarStartTime; planet.bHangarQueue = "[ { test: 1234 } ]"; planet.bHangarStartTime = 1; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); return request .post("/v1/buildings/build") @@ -368,19 +354,19 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: Globals.Buildings.ROBOTIC_FACTORY }) .then(async res => { expect(res.body.error).equals("Can't build this building while it is in use"); - expect(res.status).equals(Globals.Statuscode.SUCCESS); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); // reset planet planet.bHangarStartTime = valueBefore; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); }); }); it("should fail (can't build research-lab while it is being used)", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); - const user: User = await container.userService.getAuthenticatedUser(planet.ownerID); + const planet: Planet = await planetRepository.getById(planetID); + const user: User = await userRepository.getById(planet.ownerID); const techIDold = user.bTechID; const endtime = user.bTechEndTime; @@ -388,7 +374,7 @@ describe("buildingsRoute", () => { user.bTechEndTime = 1; user.bTechID = 109; - await container.userService.updateUserData(user); + await userRepository.save(user); return request .post("/v1/buildings/build") @@ -396,12 +382,12 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: Globals.Buildings.RESEARCH_LAB }) .then(async res => { expect(res.body.error).to.be.equal("Can't build this building while it is in use"); - expect(res.status).equals(Globals.Statuscode.SUCCESS); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); user.bTechEndTime = techIDold; user.bTechID = endtime; - await container.userService.updateUserData(user); + await userRepository.save(user); }); }); @@ -413,33 +399,31 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID: `${planetID}`, buildingID: Globals.Buildings.TERRAFORMER }) .then(async res => { - expect(res.body.error).equals("Requirements are not met"); - expect(res.status).equals(Globals.Statuscode.SUCCESS); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("should fail (planet has not enough resources)", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planet: Planet = await planetRepository.getById(planetID); const metalBefore = planet.metal; planet.metal = 0; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); return request .post("/v1/buildings/build") .set("Authorization", authToken) .send({ planetID: `${planetID}`, buildingID: Globals.Buildings.METAL_MINE }) .then(async res => { - expect(res.body.error).equals("Not enough resources"); - expect(res.status).equals(Globals.Statuscode.SUCCESS); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); // reset planet planet.metal = metalBefore; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); }); }); }); @@ -450,8 +434,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ buildingID: Globals.Buildings.METAL_MINE }) .then(async res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -463,8 +446,7 @@ describe("buildingsRoute", () => { .set("Authorization", authToken) .send({ planetID: `${planetID}` }) .then(async res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -473,8 +455,7 @@ describe("buildingsRoute", () => { .post("/v1/buildings/demolish") .set("Authorization", authToken) .then(async res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -484,23 +465,21 @@ describe("buildingsRoute", () => { return request .post("/v1/buildings/demolish") .set("Authorization", authToken) - .send({ planetID: `${planetID}`, buildingID: 503 }) + .send({ planetID: planetID, buildingID: 503 }) .then(async res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("should fail (player does not own planet)", async () => { - const planetID = 1234; + const planetID = 87851; return request .post("/v1/buildings/demolish") .set("Authorization", authToken) .send({ planetID: `${planetID}`, buildingID: Globals.Buildings.METAL_MINE }) .then(async res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.NOT_AUTHORIZED); }); }); @@ -513,14 +492,14 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: Globals.Buildings.MISSILE_SILO }) .then(async res => { expect(res.body.error).equals("This building can't be demolished"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("should start demolition of a building", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planet: Planet = await planetRepository.getById(planetID); return request .post("/v1/buildings/demolish") @@ -531,23 +510,23 @@ describe("buildingsRoute", () => { expect(res.body.bBuildingId).greaterThan(0); expect(res.body.bBuildingEndTime).greaterThan(0); expect(res.body.bBuildingDemolition).equals(true); - expect(res.status).equals(Globals.Statuscode.SUCCESS); + expect(res.status).equals(Globals.StatusCodes.SUCCESS); // reset - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); }); }); it("should fail (planet has already a build job)", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planet: Planet = await planetRepository.getById(planetID); planet.bBuildingId = 1; planet.bBuildingEndTime = 1234; planet.bBuildingDemolition = true; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); return request .post("/v1/buildings/demolish") @@ -555,13 +534,13 @@ describe("buildingsRoute", () => { .send({ planetID: `${planetID}`, buildingID: Globals.Buildings.METAL_MINE }) .then(async res => { expect(res.body.error).equals("Planet already has a build-job"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); // reset planet.bBuildingId = 0; planet.bBuildingEndTime = 0; planet.bBuildingDemolition = false; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); }); }); }); diff --git a/src/routes/BuildingsRouter.ts b/src/routes/BuildingsRouter.ts index e6507b1..ce99dee 100644 --- a/src/routes/BuildingsRouter.ts +++ b/src/routes/BuildingsRouter.ts @@ -1,332 +1,111 @@ -import { Response, Router } from "express"; -import Calculations from "../common/Calculations"; -import Config from "../common/Config"; import { Globals } from "../common/Globals"; import InputValidator from "../common/InputValidator"; -import ILogger from "../interfaces/ILogger"; -import IBuildingService from "../interfaces/IBuildingService"; -import IPlanetService from "../interfaces/IPlanetService"; -import IUserService from "../interfaces/IUserService"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; +import IBuildingService from "../interfaces/services/IBuildingService"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import IUserService from "../interfaces/services/IUserService"; + import Planet from "../units/Planet"; import Buildings from "../units/Buildings"; -import User from "../units/User"; -import ICosts from "../interfaces/ICosts"; - -/** - * Defines routes for building-data - */ -export default class BuildingsRouter { - public router: Router = Router(); - - private logger: ILogger; - - private buildingService: IBuildingService; - private planetService: IPlanetService; - private userService: IUserService; - - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.buildingService = container.buildingService; - this.planetService = container.planetService; - this.userService = container.userService; - this.router.post("/build", this.startBuilding); - this.router.post("/cancel", this.cancelBuilding); - this.router.post("/demolish", this.demolishBuilding); - this.router.get("/:planetID", this.getAllBuildingsOnPlanet); - - this.logger = logger; - } - - /** - * Returns all buildings on a given planet - * @param request - * @param response - * @param next - */ - public getAllBuildingsOnPlanet = async (request: IAuthorizedRequest, response: Response) => { +import { inject } from "inversify"; +import TYPES from "../ioc/types"; +import { Body, Controller, Get, Post, Request, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; + +import CancelBuildingRequest from "../entities/requests/CancelBuildingRequest"; +import BuildBuildingRequest from "../entities/requests/BuildBuildingRequest"; +import DemolishBuildingRequest from "../entities/requests/DemolishBuildingRequest"; +import FailureResponse from "../entities/responses/FailureResponse"; + +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("buildings") +@Tags("Buildings") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(BuildingsRouter) +export class BuildingsRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IBuildingService) private buildingService: IBuildingService; + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + @inject(TYPES.IUserService) private userService: IUserService; + + @Get("/{planetID}") + @Security("jwt") + public async getAllBuildingsOnPlanet( + @Request() request, + planetID: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(request.params.planetID) || !InputValidator.isValidInt(request.params.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const planetID: number = parseInt(request.params.planetID, 10); - - // TODO: check if user owns the planet - const data = await this.buildingService.getBuildings(planetID); - - return response.status(Globals.Statuscode.SUCCESS).json(data ?? {}); + return successResponse( + Globals.StatusCodes.SUCCESS, + await this.buildingService.getAll(planetID, request.user.userID), + ); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Cancels a build-order on a planet - * @param request - * @param response - * @param next - */ - public cancelBuilding = async (request: IAuthorizedRequest, response: Response) => { + @Post("/build") + @Security("jwt") + public async startBuilding( + @Request() headers, + @Body() request: BuildBuildingRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(request.body.planetID) || !InputValidator.isValidInt(request.body.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - const buildings: Buildings = await this.buildingService.getBuildings(planetID); - - if (!InputValidator.isSet(planet) || !InputValidator.isSet(buildings)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); + if (!InputValidator.isValidBuildingId(request.buildingID)) { + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse("Invalid parameter")); } - if (!planet.isUpgradingBuilding()) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Planet has no build-job", - }); - } - - const buildingKey = Globals.UnitNames[planet.bBuildingId]; - - const currentLevel = buildings[buildingKey]; - - const cost: ICosts = Calculations.getCosts(planet.bBuildingId, currentLevel); - - planet.bBuildingId = 0; - planet.bBuildingEndTime = 0; - planet.metal = planet.metal + cost.metal; - planet.crystal = planet.crystal + cost.crystal; - planet.crystal = planet.crystal + cost.crystal; - - await this.planetService.updatePlanet(planet); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.buildingService.start(request, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Starts a new build-order - * @param request - * @param response - * @param next - */ - public startBuilding = async (request: IAuthorizedRequest, response: Response) => { + @Post("/cancel") + @Security("jwt") + public async cancelBuilding( + @Request() headers, + @Body() request: CancelBuildingRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if ( - !InputValidator.isSet(request.body.planetID) || - !InputValidator.isValidInt(request.body.planetID) || - !InputValidator.isSet(request.body.buildingID) || - !InputValidator.isValidInt(request.body.buildingID) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - const buildingID = parseInt(request.body.buildingID, 10); - - if (!InputValidator.isValidBuildingId(buildingID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - const buildings: Buildings = await this.buildingService.getBuildings(planetID); - const user: User = await this.userService.getAuthenticatedUser(userID); - - if (!InputValidator.isSet(planet) || !InputValidator.isSet(buildings)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - // 1. check if there is already a build-job on the planet - if (planet.isUpgradingBuilding()) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Planet already has a build-job", - }); - } - - // can't build shipyard / robotic / nanite while ships or defenses are built - if ( - (buildingID === Globals.Buildings.ROBOTIC_FACTORY || - buildingID === Globals.Buildings.NANITE_FACTORY || - buildingID === Globals.Buildings.SHIPYARD) && - InputValidator.isSet(planet.bHangarQueue) && - planet.isBuildingUnits() - ) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Can't build this building while it is in use", - }); - } - - // can't build research lab while they are researching... poor scientists :( - if (buildingID === Globals.Buildings.RESEARCH_LAB && user.isResearching()) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Can't build this building while it is in use", - }); - } - - // 2. check, if requirements are met - const requirements = Config.getGameConfig().units.buildings.find(r => r.unitID === buildingID).requirements; - - // TODO: move to seperate file - // building has requirements - if (requirements !== undefined) { - requirements.forEach(function(requirement) { - const key = Globals.UnitNames[requirement.unitID]; - - if (buildings[key] < requirement.level) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Requirements are not met", - }); - } - }); - } - - // 3. check if there are enough resources on the planet for the building to be built - const buildingKey = Globals.UnitNames[buildingID]; - const currentLevel = buildings[buildingKey]; - - const cost = Calculations.getCosts(buildingID, currentLevel); - - if ( - planet.metal < cost.metal || - planet.crystal < cost.crystal || - planet.deuterium < cost.deuterium || - planet.energyUsed < cost.energy - ) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Not enough resources", - }); - } - - // 4. start the build-job - const buildTime: number = Calculations.calculateBuildTimeInSeconds( - cost.metal, - cost.crystal, - buildings.roboticFactory, - buildings.naniteFactory, - ); - - const endTime: number = Math.round(+new Date() / 1000) + buildTime; - - planet.metal = planet.metal - cost.metal; - planet.crystal = planet.crystal - cost.crystal; - planet.deuterium = planet.deuterium - cost.deuterium; - planet.bBuildingId = buildingID; - planet.bBuildingEndTime = endTime; - - await this.planetService.updatePlanet(planet); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.buildingService.cancel(request.planetID, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - public demolishBuilding = async (request: IAuthorizedRequest, response: Response) => { + @Post("/demolish") + @Security("jwt") + public async demolishBuilding( + @Request() headers, + @Body() request: DemolishBuildingRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if ( - !InputValidator.isSet(request.body.planetID) || - !InputValidator.isValidInt(request.body.planetID) || - !InputValidator.isSet(request.body.buildingID) || - !InputValidator.isValidInt(request.body.buildingID) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); + if (!InputValidator.isValidBuildingId(request.buildingID)) { + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse("Invalid parameter")); } - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - const buildingID = parseInt(request.body.buildingID, 10); - - if (!InputValidator.isValidBuildingId(buildingID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - const buildings: Buildings = await this.buildingService.getBuildings(planetID); - - if (!InputValidator.isSet(planet) || !InputValidator.isSet(buildings)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - if (planet.isUpgradingBuilding()) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Planet already has a build-job", - }); - } - - const buildingKey = Globals.UnitNames[buildingID]; - const currentLevel = buildings[buildingKey]; - - if (currentLevel === 0) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "This building can't be demolished", - }); - } - - const cost = Calculations.getCosts(buildingID, currentLevel - 1); - - const buildTime: number = Calculations.calculateBuildTimeInSeconds( - cost.metal, - cost.crystal, - buildings.roboticFactory, - buildings.naniteFactory, - ); - - const endTime: number = Math.round(+new Date() / 1000) + buildTime; - - planet.bBuildingId = buildingID; - planet.bBuildingEndTime = endTime; - planet.bBuildingDemolition = true; - - await this.planetService.updatePlanet(planet); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.buildingService.demolish(request, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/ConfigRouter.spec.ts b/src/routes/ConfigRouter.spec.ts index 02f9acf..ff58da2 100644 --- a/src/routes/ConfigRouter.spec.ts +++ b/src/routes/ConfigRouter.spec.ts @@ -2,15 +2,9 @@ import * as chai from "chai"; import chaiHttp = require("chai-http"); import App from "../App"; -import SimpleLogger from "../loggers/SimpleLogger"; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); import Config from "../common/Config"; -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; diff --git a/src/routes/ConfigRouter.ts b/src/routes/ConfigRouter.ts index d49c43b..8d31673 100644 --- a/src/routes/ConfigRouter.ts +++ b/src/routes/ConfigRouter.ts @@ -1,64 +1,47 @@ -import { Request, Response, Router } from "express"; import { Globals } from "../common/Globals"; -import ILogger from "../interfaces/ILogger"; -import Config from "../common/Config"; - -/** - * Defines routes to get the config-files - */ -export default class ConfigRouter { - public router: Router = Router(); - - private logger: ILogger; - - /** - * Initialize the Router - * @param logger Instance of an ILogger-object - */ - public constructor(logger: ILogger) { - this.router.get("/game", this.getGameConfig); - this.router.get("/units", this.getUnitsConfig); - - this.logger = logger; - } - /** - * Returns the current game-configuration - * @param req - * @param response - * @param next - */ - public getGameConfig(req: Request, response: Response) { +import Config from "../common/Config"; +import { Controller, Get, Res, Route, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; +import FailureResponse from "../entities/responses/FailureResponse"; + +import IGameConfig, { IUnits } from "../interfaces/IGameConfig"; +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("config") +@Tags("Configuration") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(ConfigRouter) +export class ConfigRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @Get("/game") + public getGameConfig( + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - const data = Config.getGameConfig(); - - return response.status(Globals.Statuscode.SUCCESS).json(data ?? {}); + return successResponse(Globals.StatusCodes.SUCCESS, Config.getGameConfig()); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } } - /** - * Returns the current unit-configuration - * @param req - * @param response - * @param next - */ - public getUnitsConfig(req: Request, response: Response) { + @Get("/units") + public getUnitsConfig( + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - const data = Config.getGameConfig(); - - return response.status(Globals.Statuscode.SUCCESS).json(data.units ?? {}); + return successResponse(Globals.StatusCodes.SUCCESS, Config.getGameConfig().units); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } } } diff --git a/src/routes/DefenseRouter.spec.ts b/src/routes/DefenseRouter.spec.ts index 2444a95..1938dda 100644 --- a/src/routes/DefenseRouter.spec.ts +++ b/src/routes/DefenseRouter.spec.ts @@ -4,14 +4,14 @@ import chaiHttp = require("chai-http"); import App from "../App"; import { Globals } from "../common/Globals"; import Planet from "../units/Planet"; -import SimpleLogger from "../loggers/SimpleLogger"; +import { iocContainer } from "../ioc/inversify.config"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; -const container = createContainer(); +const planetRepository = iocContainer.get(TYPES.IPlanetRepository); -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -23,9 +23,9 @@ describe("defenseRoute", () => { let planetBeforeTests: Planet; before(async () => { - planetBeforeTests = await container.planetService.getPlanet(1, 167546850, true); + planetBeforeTests = await planetRepository.getById(167546850); return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -33,7 +33,7 @@ describe("defenseRoute", () => { }); after(async () => { - await container.planetService.updatePlanet(planetBeforeTests); + await planetRepository.save(planetBeforeTests); }); beforeEach(function() { @@ -48,22 +48,21 @@ describe("defenseRoute", () => { .get(`/v1/defenses/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.planetID).equals(planetID); }); }); - it("should return an empty list", () => { + it("should fail, planet does not exist", () => { const planetID = 60881; return request .get(`/v1/defenses/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body).to.be.empty; }); }); @@ -72,8 +71,7 @@ describe("defenseRoute", () => { .get("/v1/defenses/asdf") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -83,9 +81,17 @@ describe("defenseRoute", () => { return request .post("/v1/defenses/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: JSON.stringify({ 301: 1, 302: 1, 309: 1, 310: 1 }) }) + .send({ + planetID: planetID, + buildOrder: [ + { unitID: 301, amount: 1 }, + { unitID: 302, amount: 1 }, + { unitID: 309, amount: 1 }, + { unitID: 310, amount: 1 }, + ], + }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.planetID).equals(planetID); }); @@ -97,9 +103,9 @@ describe("defenseRoute", () => { return request .post("/v1/defenses/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: JSON.stringify({ 310: 10000 }) }) + .send({ planetID, buildOrder: [{ unitID: 310, amount: 10000 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.planetID).equals(planetID); }); @@ -109,11 +115,10 @@ describe("defenseRoute", () => { return request .post("/v1/defenses/build") .set("Authorization", authToken) - .send({ buildOrder: JSON.stringify({ 301: 1 }) }) + .send({ buildOrder: [{ unitID: 301, amount: 1 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -125,9 +130,9 @@ describe("defenseRoute", () => { .set("Authorization", authToken) .send({ planetID }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -136,9 +141,9 @@ describe("defenseRoute", () => { .post("/v1/defenses/build") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -152,8 +157,9 @@ describe("defenseRoute", () => { // eslint-disable-next-line prettier/prettier .send({ planetID, buildOrder: "{ \"xyz\": 1 }" }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(res.type).to.eql("application/json"); + expect(res.body.error).to.be.equals("Validation failed"); }) ); }); @@ -166,10 +172,10 @@ describe("defenseRoute", () => { .post("/v1/defenses/build") .set("Authorization", authToken) // eslint-disable-next-line prettier/prettier - .send({ planetID, buildOrder: "{ \"301\": 1 }" }) + .send({ planetID, buildOrder: [{ unitID: 301, amount: 1 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("The player does not own the planet"); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(res.body.error).to.be.equals("Planet does not exist"); }) ); }); @@ -177,26 +183,26 @@ describe("defenseRoute", () => { it("should fail (shipyard is currently upgrading)", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planet: Planet = await planetRepository.getById(planetID); const valueBefore = planet.bHangarPlus; planet.bHangarPlus = true; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); return ( request .post("/v1/defenses/build") .set("Authorization", authToken) // eslint-disable-next-line prettier/prettier - .send({ planetID, buildOrder: "{ \"301\": 1 }" }) + .send({ planetID, buildOrder: [{ unitID: 301, amount: 1 }] }) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.body.error).to.be.equals("Shipyard is currently upgrading"); planet.bHangarPlus = valueBefore; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); }) ); }); diff --git a/src/routes/DefenseRouter.ts b/src/routes/DefenseRouter.ts index a1a1f6d..eb27089 100644 --- a/src/routes/DefenseRouter.ts +++ b/src/routes/DefenseRouter.ts @@ -1,251 +1,72 @@ -import { Response, Router } from "express"; -import Calculations from "../common/Calculations"; import { Globals } from "../common/Globals"; import InputValidator from "../common/InputValidator"; -import Queue from "../common/Queue"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import IBuildingService from "../interfaces/IBuildingService"; -import ICosts from "../interfaces/ICosts"; -import IDefenseService from "../interfaces/IDefenseService"; -import IPlanetService from "../interfaces/IPlanetService"; -import Buildings from "../units/Buildings"; -import Defenses from "../units/Defenses"; -import Planet from "../units/Planet"; -import QueueItem from "../common/QueueItem"; -import ILogger from "../interfaces/ILogger"; -/** - * Defines routes for defense-data - */ -export default class DefenseRouter { - public router: Router = Router(); +import IBuildingService from "../interfaces/services/IBuildingService"; - private logger: ILogger; +import IDefenseService from "../interfaces/services/IDefenseService"; +import IPlanetService from "../interfaces/services/IPlanetService"; - private planetService: IPlanetService; - private buildingService: IBuildingService; - private defenseService: IDefenseService; +import Defenses from "../units/Defenses"; - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.planetService = container.planetService; - this.buildingService = container.buildingService; - this.defenseService = container.defenseService; +import { Body, Controller, Get, Post, Request, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; - this.router.get("/:planetID", this.getAllDefensesOnPlanet); - this.router.post("/build/", this.buildDefense); +import BuildDefenseRequest from "../entities/requests/BuildDefenseRequest"; +import FailureResponse from "../entities/responses/FailureResponse"; - this.logger = logger; - } +import Planet from "../units/Planet"; - /** - * Returns a list of defenses on a given planet - * @param request - * @param response - * @param next - */ - public getAllDefensesOnPlanet = async (request: IAuthorizedRequest, response: Response) => { +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("defenses") +@Tags("Defenses") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(DefenseRouter) +export class DefenseRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IBuildingService) private buildingService: IBuildingService; + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + @inject(TYPES.IDefenseService) private defenseService: IDefenseService; + + @Get("/{planetID}") + @Security("jwt") + public async getAllDefensesOnPlanet( + @Request() headers, + planetID: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(request.params.planetID) || !InputValidator.isValidInt(request.params.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const planetID = parseInt(request.params.planetID, 10); - const userID = parseInt(request.userID, 10); - - const defenses: Defenses = await this.defenseService.getDefenses(userID, planetID); - - return response.status(Globals.Statuscode.SUCCESS).json(defenses ?? {}); + return await this.defenseService.getAll(headers.user.userID, planetID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Append a new build-order to the current build-queue - * @param request - * @param response - * @param next - */ - public buildDefense = async (request: IAuthorizedRequest, response: Response) => { + @Post("/build") + @Security("jwt") + public async buildDefense( + @Request() headers, + @Body() request: BuildDefenseRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if ( - !InputValidator.isSet(request.body.planetID) || - !InputValidator.isValidInt(request.body.planetID) || - !InputValidator.isSet(request.body.buildOrder) || - !InputValidator.isValidJson(request.body.buildOrder) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - - const buildOrders = JSON.parse(request.body.buildOrder); - - const queue: Queue = new Queue(); - - // validate build-order - if (!InputValidator.isValidBuildOrder(buildOrders, Globals.UnitType.DEFENSE)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const buildings: Buildings = await this.buildingService.getBuildings(planetID); - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - const defenses: Defenses = await this.defenseService.getDefenses(userID, planetID); - - if (!InputValidator.isSet(buildings) || !InputValidator.isSet(planet)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "The player does not own the planet", - }); - } - - if (planet.isUpgradingHangar()) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Shipyard is currently upgrading", - }); - } - - let metal = planet.metal; - let crystal = planet.crystal; - let deuterium = planet.deuterium; - - let stopProcessing = false; - let buildTime = 0; - - let freeSiloSlots: number = Calculations.calculateFreeMissileSlots( - buildings.missileSilo, - defenses.antiBallisticMissile, - defenses.interplanetaryMissile, - ); - - // TODO: put this into a seperate function - for (const item in buildOrders) { - if (buildOrders.hasOwnProperty(item)) { - let count: number = buildOrders[item]; - const cost: ICosts = Calculations.getCosts(parseInt(item, 10), 1); - - // if the user has not enough ressources to fullfill the complete build-order - if (metal < cost.metal * count || crystal < cost.crystal * count || deuterium < cost.deuterium * count) { - let tempCount: number; - - if (cost.metal > 0) { - tempCount = metal / cost.metal; - - if (tempCount < count) { - count = tempCount; - } - } - - if (cost.crystal > 0) { - tempCount = crystal / cost.crystal; - - if (tempCount < count) { - count = tempCount; - } - } - - if (cost.deuterium > 0) { - tempCount = deuterium / cost.deuterium; - - if (tempCount < count) { - count = tempCount; - } - } - - // no need to further process the queue - stopProcessing = true; - } - - // check free slots in silo - if (item === "309") { - // can't build any more rockets - if (freeSiloSlots === 0) { - buildOrders[item] = 0; - } else { - buildOrders[item] = Math.min(freeSiloSlots, buildOrders[item]); - freeSiloSlots -= buildOrders[item]; - } - } - - if (item === "310") { - // can't build any more rockets - if (freeSiloSlots === 0) { - buildOrders[item] = 0; - } else { - buildOrders[item] = Math.floor(freeSiloSlots / 2) * buildOrders[item]; - freeSiloSlots -= buildOrders[item]; - } - } - - // build time in seconds - buildTime += - Calculations.calculateBuildTimeInSeconds( - cost.metal, - cost.crystal, - buildings.shipyard, - buildings.naniteFactory, - ) * Math.floor(count); - - queue.getQueue().push(new QueueItem(parseInt(item, 10), Math.floor(count))); - - metal -= cost.metal * count; - crystal -= cost.crystal * count; - deuterium -= cost.deuterium * count; - - if (stopProcessing) { - break; - } - } else { - // TODO: throw a meaningful error - throw Error(); - } - } - - queue.setTimeRemaining(buildTime); - queue.setLastUpdateTime(Math.floor(Date.now() / 1000)); - - let oldBuildOrder; - - if (!planet.isBuildingUnits()) { - planet.bHangarQueue = JSON.parse("[]"); - oldBuildOrder = planet.bHangarQueue; - planet.bHangarStartTime = Math.floor(Date.now() / 1000); - } else { - oldBuildOrder = JSON.parse(planet.bHangarQueue); + if (!InputValidator.isValidBuildOrder(request.buildOrder, Globals.UnitType.DEFENSE)) { + this.setStatus(Globals.StatusCodes.BAD_REQUEST); + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse("Invalid parameter")); } - oldBuildOrder.push(queue); - - planet.bHangarQueue = JSON.stringify(oldBuildOrder); - - planet.metal = metal; - planet.crystal = crystal; - planet.deuterium = deuterium; - - await this.planetService.updatePlanet(planet); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.defenseService.processBuildOrder(request, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/EventRouter.spec.ts b/src/routes/EventRouter.spec.ts index 83e3716..af21220 100644 --- a/src/routes/EventRouter.spec.ts +++ b/src/routes/EventRouter.spec.ts @@ -1,385 +1,379 @@ -import * as chai from "chai"; -import chaiHttp = require("chai-http"); - -import App from "../App"; -import { Globals } from "../common/Globals"; -import SimpleLogger from "../loggers/SimpleLogger"; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); - -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; - -chai.use(chaiHttp); -const expect = chai.expect; - -let authToken = ""; -let request = chai.request(app); - -describe("eventRouter", () => { - before(() => { - return request - .post("/v1/auth/login") - .send({ email: "user_1501005189510@test.com", password: "admin" }) - .then(res => { - authToken = res.body.token; - }); - }); - - beforeEach(function() { - request = chai.request(app); - return; - }); - - it("should create a new event", () => { - const eventData = `{ - "ownerID": 1, - "mission": "attack", - "speed": 30, - "holdDuration": 695, - "data": { - "origin": { - "posGalaxy": 9, - "posSystem": 54, - "posPlanet": 1, - "type": "planet" - }, - "destination": { - "posGalaxy": 4, - "posSystem": 71, - "posPlanet": 2, - "type": "planet" - }, - "ships": { - "201": 612, - "202": 357, - "203": 617, - "204": 800, - "205": 709, - "206": 204, - "207": 703, - "208": 85, - "209": 631, - "210": 388, - "211": 0, - "212": 723, - "213": 557, - "214": 106 - }, - "loadedRessources": { - "metal": 443, - "crystal": 980, - "deuterium": 220 - } - } -}`; - - return request - .post("/v1/events/create") - .send({ event: eventData }) - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); - }); - }); - - it("should fail (no data sent)", () => { - return request - .post("/v1/events/create") - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Invalid parameter"); - }); - }); - - it("should fail (invalid json / missing attributes)", () => { - const eventData = `{ - "ownerID": 1, - "mission": "attack", - "speed": 30, - "holdDuration": 695, - "data": { - "origin": { - "posGalaxy": 9, - "posSystem": 54, - "posPlanet": 1, - "type": "planet" - }, - "destination": { - "posGalaxy": 4, - "posSystem": 71, - "posPlanet": 2, - "type": "planet" - } - } -}`; - - return request - .post("/v1/events/create") - .send({ event: eventData }) - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Invalid json"); - }); - }); - - it("should fail (user is not event-creator)", () => { - const eventData = `{ - "ownerID": 3, - "mission": "attack", - "speed": 30, - "holdDuration": 695, - "data": { - "origin": { - "posGalaxy": 9, - "posSystem": 54, - "posPlanet": 1, - "type": "planet" - }, - "destination": { - "posGalaxy": 4, - "posSystem": 71, - "posPlanet": 2, - "type": "planet" - }, - "ships": { - "201": 612, - "202": 357, - "203": 617, - "204": 800, - "205": 709, - "206": 204, - "207": 703, - "208": 85, - "209": 631, - "210": 388, - "211": 0, - "212": 723, - "213": 557, - "214": 106 - }, - "loadedRessources": { - "metal": 443, - "crystal": 980, - "deuterium": 220 - } - } -}`; - - return request - .post("/v1/events/create") - .send({ event: eventData }) - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Event-creator is not currently authenticated user"); - }); - }); - - it("should fail (missiontype not yet supported)", () => { - const eventData = `{ - "ownerID": 1, - "mission": "acs", - "speed": 30, - "holdDuration": 695, - "data": { - "origin": { - "posGalaxy": 9, - "posSystem": 54, - "posPlanet": 1, - "type": "planet" - }, - "destination": { - "posGalaxy": 4, - "posSystem": 71, - "posPlanet": 2, - "type": "planet" - }, - "ships": { - "201": 612, - "202": 357, - "203": 617, - "204": 800, - "205": 709, - "206": 204, - "207": 703, - "208": 85, - "209": 631, - "210": 388, - "211": 0, - "212": 723, - "213": 557, - "214": 106 - }, - "loadedRessources": { - "metal": 443, - "crystal": 980, - "deuterium": 220 - } - } -}`; - - return request - .post("/v1/events/create") - .send({ event: eventData }) - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Missiontype not yet supported"); - }); - }); - - it("should fail (player does not own startplanet)", () => { - const eventData = `{ - "ownerID": 1, - "mission": "transport", - "speed": 30, - "holdDuration": 695, - "data": { - "origin": { - "posGalaxy": 6, - "posSystem": 46, - "posPlanet": 7, - "type": "planet" - }, - "destination": { - "posGalaxy": 4, - "posSystem": 71, - "posPlanet": 2, - "type": "planet" - }, - "ships": { - "201": 612, - "202": 357, - "203": 617, - "204": 800, - "205": 709, - "206": 204, - "207": 703, - "208": 85, - "209": 631, - "210": 388, - "211": 0, - "212": 723, - "213": 557, - "214": 106 - }, - "loadedRessources": { - "metal": 443, - "crystal": 980, - "deuterium": 220 - } - } -}`; - - return request - .post("/v1/events/create") - .send({ event: eventData }) - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Origin does not exist or user is not the owner"); - }); - }); - - it("should fail (destination does not exist)", () => { - const eventData = `{ - "ownerID": 1, - "mission": "transport", - "speed": 30, - "holdDuration": 695, - "data": { - "origin": { - "posGalaxy": 9, - "posSystem": 54, - "posPlanet": 1, - "type": "planet" - }, - "destination": { - "posGalaxy": 1, - "posSystem": 1, - "posPlanet": 1, - "type": "planet" - }, - "ships": { - "201": 612, - "202": 357, - "203": 617, - "204": 800, - "205": 709, - "206": 204, - "207": 703, - "208": 85, - "209": 631, - "210": 388, - "211": 0, - "212": 723, - "213": 557, - "214": 106 - }, - "loadedRessources": { - "metal": 443, - "crystal": 980, - "deuterium": 220 - } - } -}`; - - return request - .post("/v1/events/create") - .send({ event: eventData }) - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Destination does not exist"); - }); - }); - - it("should cancel an event", () => { - return request - .post("/v1/events/cancel") - .send({ eventID: 1 }) - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); - }); - }); - - it("should fail (no parameter sent)", () => { - return request - .post("/v1/events/cancel") - .set("Authorization", authToken) - .then(res => { - expect(res.body.error).to.be.equals("Invalid parameter"); - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - }); - }); - - it("should fail (invalid parameter sent)", () => { - return request - .post("/v1/events/cancel") - .set("Authorization", authToken) - .send({ eventID: "asdf" }) - .then(res => { - expect(res.body.error).to.be.equals("Invalid parameter"); - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - }); - }); - - it("should fail (event does not exist)", () => { - return request - .post("/v1/events/cancel") - .set("Authorization", authToken) - .send({ eventID: 33580 }) - .then(res => { - expect(res.body.error).to.be.equals("The event does not exist or can't be canceled"); - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - }); - }); - - // TODO: check, if event really is inQueue and/or returning -}); +// import * as chai from "chai"; +// import chaiHttp = require("chai-http"); +// +// import App from "../App"; +// import { Globals } from "../common/Globals"; +// +// const app = new App().express; +// +// chai.use(chaiHttp); +// const expect = chai.expect; +// +// let authToken = ""; +// let request = chai.request(app); +// +// describe("eventRouter", () => { +// before(() => { +// return request +// .post("/v1/login") +// .send({ email: "user_1501005189510@test.com", password: "admin" }) +// .then(res => { +// authToken = res.body.token; +// }); +// }); +// +// beforeEach(function() { +// request = chai.request(app); +// return; +// }); +// +// it("should create a new event", () => { +// const eventData = `{ +// "ownerID": 1, +// "mission": "attack", +// "speed": 30, +// "holdDuration": 695, +// "data": { +// "origin": { +// "posGalaxy": 9, +// "posSystem": 54, +// "posPlanet": 1, +// "type": "planet" +// }, +// "destination": { +// "posGalaxy": 4, +// "posSystem": 71, +// "posPlanet": 2, +// "type": "planet" +// }, +// "ships": { +// "201": 612, +// "202": 357, +// "203": 617, +// "204": 800, +// "205": 709, +// "206": 204, +// "207": 703, +// "208": 85, +// "209": 631, +// "210": 388, +// "211": 0, +// "212": 723, +// "213": 557, +// "214": 106 +// }, +// "loadedRessources": { +// "metal": 443, +// "crystal": 980, +// "deuterium": 220 +// } +// } +// }`; +// +// return request +// .post("/v1/events/create") +// .send({ event: eventData }) +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); +// }); +// }); +// +// it("should fail (no data sent)", () => { +// return request +// .post("/v1/events/create") +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// expect(res.body.error).to.be.equals("Invalid parameter"); +// }); +// }); +// +// it("should fail (invalid json / missing attributes)", () => { +// const eventData = `{ +// "ownerID": 1, +// "mission": "attack", +// "speed": 30, +// "holdDuration": 695, +// "data": { +// "origin": { +// "posGalaxy": 9, +// "posSystem": 54, +// "posPlanet": 1, +// "type": "planet" +// }, +// "destination": { +// "posGalaxy": 4, +// "posSystem": 71, +// "posPlanet": 2, +// "type": "planet" +// } +// } +// }`; +// +// return request +// .post("/v1/events/create") +// .send({ event: eventData }) +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// expect(res.body.error).to.be.equals("Invalid json"); +// }); +// }); +// +// it("should fail (user is not event-creator)", () => { +// const eventData = `{ +// "ownerID": 3, +// "mission": "attack", +// "speed": 30, +// "holdDuration": 695, +// "data": { +// "origin": { +// "posGalaxy": 9, +// "posSystem": 54, +// "posPlanet": 1, +// "type": "planet" +// }, +// "destination": { +// "posGalaxy": 4, +// "posSystem": 71, +// "posPlanet": 2, +// "type": "planet" +// }, +// "ships": { +// "201": 612, +// "202": 357, +// "203": 617, +// "204": 800, +// "205": 709, +// "206": 204, +// "207": 703, +// "208": 85, +// "209": 631, +// "210": 388, +// "211": 0, +// "212": 723, +// "213": 557, +// "214": 106 +// }, +// "loadedRessources": { +// "metal": 443, +// "crystal": 980, +// "deuterium": 220 +// } +// } +// }`; +// +// return request +// .post("/v1/events/create") +// .send({ event: eventData }) +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// expect(res.body.error).to.be.equals("Event-creator is not currently authenticated user"); +// }); +// }); +// +// it("should fail (missiontype not yet supported)", () => { +// const eventData = `{ +// "ownerID": 1, +// "mission": "acs", +// "speed": 30, +// "holdDuration": 695, +// "data": { +// "origin": { +// "posGalaxy": 9, +// "posSystem": 54, +// "posPlanet": 1, +// "type": "planet" +// }, +// "destination": { +// "posGalaxy": 4, +// "posSystem": 71, +// "posPlanet": 2, +// "type": "planet" +// }, +// "ships": { +// "201": 612, +// "202": 357, +// "203": 617, +// "204": 800, +// "205": 709, +// "206": 204, +// "207": 703, +// "208": 85, +// "209": 631, +// "210": 388, +// "211": 0, +// "212": 723, +// "213": 557, +// "214": 106 +// }, +// "loadedRessources": { +// "metal": 443, +// "crystal": 980, +// "deuterium": 220 +// } +// } +// }`; +// +// return request +// .post("/v1/events/create") +// .send({ event: eventData }) +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// expect(res.body.error).to.be.equals("Missiontype not yet supported"); +// }); +// }); +// +// it("should fail (player does not own startplanet)", () => { +// const eventData = `{ +// "ownerID": 1, +// "mission": "transport", +// "speed": 30, +// "holdDuration": 695, +// "data": { +// "origin": { +// "posGalaxy": 6, +// "posSystem": 46, +// "posPlanet": 7, +// "type": "planet" +// }, +// "destination": { +// "posGalaxy": 4, +// "posSystem": 71, +// "posPlanet": 2, +// "type": "planet" +// }, +// "ships": { +// "201": 612, +// "202": 357, +// "203": 617, +// "204": 800, +// "205": 709, +// "206": 204, +// "207": 703, +// "208": 85, +// "209": 631, +// "210": 388, +// "211": 0, +// "212": 723, +// "213": 557, +// "214": 106 +// }, +// "loadedRessources": { +// "metal": 443, +// "crystal": 980, +// "deuterium": 220 +// } +// } +// }`; +// +// return request +// .post("/v1/events/create") +// .send({ event: eventData }) +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// expect(res.body.error).to.be.equals("Origin does not exist or user is not the owner"); +// }); +// }); +// +// it("should fail (destination does not exist)", () => { +// const eventData = `{ +// "ownerID": 1, +// "mission": "transport", +// "speed": 30, +// "holdDuration": 695, +// "data": { +// "origin": { +// "posGalaxy": 9, +// "posSystem": 54, +// "posPlanet": 1, +// "type": "planet" +// }, +// "destination": { +// "posGalaxy": 1, +// "posSystem": 1, +// "posPlanet": 1, +// "type": "planet" +// }, +// "ships": { +// "201": 612, +// "202": 357, +// "203": 617, +// "204": 800, +// "205": 709, +// "206": 204, +// "207": 703, +// "208": 85, +// "209": 631, +// "210": 388, +// "211": 0, +// "212": 723, +// "213": 557, +// "214": 106 +// }, +// "loadedRessources": { +// "metal": 443, +// "crystal": 980, +// "deuterium": 220 +// } +// } +// }`; +// +// return request +// .post("/v1/events/create") +// .send({ event: eventData }) +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// expect(res.body.error).to.be.equals("Destination does not exist"); +// }); +// }); +// +// it("should cancel an event", () => { +// return request +// .post("/v1/events/cancel") +// .send({ eventID: 1 }) +// .set("Authorization", authToken) +// .then(res => { +// expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); +// }); +// }); +// +// it("should fail (no parameter sent)", () => { +// return request +// .post("/v1/events/cancel") +// .set("Authorization", authToken) +// .then(res => { +// expect(res.body.error).to.be.equals("Invalid parameter"); +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// }); +// }); +// +// it("should fail (invalid parameter sent)", () => { +// return request +// .post("/v1/events/cancel") +// .set("Authorization", authToken) +// .send({ eventID: "asdf" }) +// .then(res => { +// expect(res.body.error).to.be.equals("Invalid parameter"); +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// }); +// }); +// +// it("should fail (event does not exist)", () => { +// return request +// .post("/v1/events/cancel") +// .set("Authorization", authToken) +// .send({ eventID: 33580 }) +// .then(res => { +// expect(res.body.error).to.be.equals("The event does not exist or can't be canceled"); +// expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); +// }); +// }); +// +// // TODO: check, if event really is inQueue and/or returning +// }); diff --git a/src/routes/EventRouter.ts b/src/routes/EventRouter.ts index a9bb7f2..c5cf2c0 100644 --- a/src/routes/EventRouter.ts +++ b/src/routes/EventRouter.ts @@ -1,19 +1,16 @@ -import { Response, Router } from "express"; -import Calculations from "../common/Calculations"; -import Config from "../common/Config"; import { Globals } from "../common/Globals"; -import InputValidator from "../common/InputValidator"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import ICoordinates from "../interfaces/ICoordinates"; -import IEventService from "../interfaces/IEventService"; -import IPlanetService from "../interfaces/IPlanetService"; + +import IEventService from "../interfaces/services/IEventService"; +import IPlanetService from "../interfaces/services/IPlanetService"; import Event from "../units/Event"; -import ILogger from "../interfaces/ILogger"; -const validator = require("jsonschema").Validator; -const jsonValidator = new validator(); +import { Body, Post, Request, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; +import IErrorHandler from "../interfaces/IErrorHandler"; -import * as eventSchema from "../schemas/fleetevent.schema.json"; +import FailureResponse from "../entities/responses/FailureResponse"; // TODO: validate input data: // is start != end? @@ -21,197 +18,194 @@ import * as eventSchema from "../schemas/fleetevent.schema.json"; // units.json => check all values (capacity, etc). // loaded resources > storage? -/** - * Defines routes for event-creation and cancellation - */ +@Route("event") +@Tags("Events") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(EventRouter) export default class EventRouter { - public router: Router = Router(); - - private logger: ILogger; - - private planetService: IPlanetService; - private eventService: IEventService; - - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.planetService = container.planetService; - this.eventService = container.eventService; - - this.router.post("/create/", this.createEvent); - this.router.post("/cancel/", this.cancelEvent); - - this.logger = logger; - } - - /** - * Creates a new event - * @param request - * @param response - * @param next - */ - public createEvent = async (request: IAuthorizedRequest, response: Response) => { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + @inject(TYPES.IEventService) private eventService: IEventService; + + @Post("/create") + @Security("jwt") + public async createEvent( + @Request() headers, + @Body() request: Event, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - // TODO: check if enough ships on planet - // TODO: check if planet has enough deuterium - - if (!InputValidator.isSet(request.body.event)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const eventData = JSON.parse(request.body.event); - - // validate JSON against schema - if (!jsonValidator.validate(eventData, eventSchema).valid) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid json", - }); - } - - const userID = parseInt(request.userID, 10); - const ownerID = parseInt(eventData.ownerID, 10); - - // check if sender of event == currently authenticated user - if (userID !== ownerID) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Event-creator is not currently authenticated user", - }); - } - - // TODO: temporary - if (["deploy", "acs", "hold", "harvest", "espionage", "destroy"].indexOf(eventData.mission) >= 0) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Missiontype not yet supported", - }); - } - - const positionOrigin: ICoordinates = { - posGalaxy: eventData.data.origin.posGalaxy, - posSystem: eventData.data.origin.posSystem, - posPlanet: eventData.data.origin.posPlanet, - type: this.getDestinationTypeByName(eventData.data.origin.type), - }; - - const positionDestination: ICoordinates = { - posGalaxy: eventData.data.destination.posGalaxy, - posSystem: eventData.data.destination.posSystem, - posPlanet: eventData.data.destination.posPlanet, - type: this.getDestinationTypeByName(eventData.data.destination.type), - }; - - const startPlanet = await this.planetService.getPlanetOrMoonAtPosition(positionOrigin); - const destinationPlanet = await this.planetService.getPlanetOrMoonAtPosition(positionDestination); - - if (!InputValidator.isSet(startPlanet) || startPlanet.ownerID !== userID) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Origin does not exist or user is not the owner", - }); - } - - // destination does not exist - if (!InputValidator.isSet(destinationPlanet) && eventData.mission !== "colonize") { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Destination does not exist", - }); - } - - const distance = Calculations.calculateDistance(eventData.data.origin, eventData.data.destination); - - const gameConfig = Config.getGameConfig(); - - const slowestShipSpeed = Calculations.getSlowestShipSpeed(eventData.data.ships); - - // calculate duration of flight - const timeOfFlight = Calculations.calculateTimeOfFlight( - gameConfig.server.speed, - eventData.speed, - distance, - slowestShipSpeed, - ); - - const event: Event = new Event(); - - event.eventID = 0; - event.ownerID = eventData.ownerID; - event.mission = this.getMissionTypeID(eventData.mission); - event.fleetlist = JSON.stringify(eventData.data.ships); - event.startID = startPlanet.planetID; - event.startType = this.getDestinationTypeByName(eventData.data.origin.type); - event.startTime = Math.round(+new Date() / 1000); - event.endID = destinationPlanet.planetID; - event.endType = this.getDestinationTypeByName(eventData.data.destination.type); - event.endTime = Math.round(event.startTime + timeOfFlight); - event.loadedMetal = eventData.data.loadedRessources.metal; - event.loadedCrystal = eventData.data.loadedRessources.crystal; - event.loadedDeuterium = eventData.data.loadedRessources.deuterium; - event.inQueue = false; - event.returning = false; - event.processed = false; - - await this.eventService.createNewEvent(event); - - // all done - return response.status(Globals.Statuscode.SUCCESS).json(event ?? {}); + return await this.eventService.create(request); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; - /** - * Cancels an event - * @param request - * @param response - * @param next - */ - public cancelEvent = async (request: IAuthorizedRequest, response: Response) => { - try { - if (!InputValidator.isSet(request.body.eventID) || !InputValidator.isValidInt(request.body.eventID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const eventID = parseInt(request.body.eventID, 10); - - const event: Event = await this.eventService.getEventOfPlayer(userID, eventID); - - if (!InputValidator.isSet(event) || event.returning === true || event.inQueue === true) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "The event does not exist or can't be canceled", - }); - } - - // (time passed from start until cancel) + (time now) - event.endTime = Math.round(+new Date() / 1000) - event.startTime + Math.round(+new Date() / 1000); - event.startTime = Math.round(+new Date() / 1000); - - await this.eventService.cancelEvent(event); + // try { + // // TODO: check if enough ships on planet + // // TODO: check if planet has enough deuterium + // + // if (!InputValidator.isSet(request.body.event)) { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "Invalid parameter", + // }); + // } + // + // const eventData = JSON.parse(request.body.event); + // + // // validate JSON against schema + // if (!jsonValidator.validate(eventData, eventSchema).valid) { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "Invalid json", + // }); + // } + // + // const userID = parseInt(request.userID, 10); + // const ownerID = parseInt(eventData.ownerID, 10); + // + // // check if sender of event == currently authenticated user + // if (userID !== ownerID) { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "Event-creator is not currently authenticated user", + // }); + // } + // + // // TODO: temporary + // if (["deploy", "acs", "hold", "harvest", "espionage", "destroy"].indexOf(eventData.mission) >= 0) { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "Missiontype not yet supported", + // }); + // } + // + // const positionOrigin: ICoordinates = { + // posGalaxy: eventData.data.origin.posGalaxy, + // posSystem: eventData.data.origin.posSystem, + // posPlanet: eventData.data.origin.posPlanet, + // type: this.getDestinationTypeByName(eventData.data.origin.type), + // }; + // + // const positionDestination: ICoordinates = { + // posGalaxy: eventData.data.destination.posGalaxy, + // posSystem: eventData.data.destination.posSystem, + // posPlanet: eventData.data.destination.posPlanet, + // type: this.getDestinationTypeByName(eventData.data.destination.type), + // }; + // + // const startPlanet = await this.planetService.getPlanetOrMoonAtPosition(positionOrigin); + // const destinationPlanet = await this.planetService.getPlanetOrMoonAtPosition(positionDestination); + // + // if (!InputValidator.isSet(startPlanet) || startPlanet.ownerID !== userID) { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "Origin does not exist or user is not the owner", + // }); + // } + // + // // destination does not exist + // if (!InputValidator.isSet(destinationPlanet) && eventData.mission !== "colonize") { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "Destination does not exist", + // }); + // } + // + // const distance = Calculations.calculateDistance(eventData.data.origin, eventData.data.destination); + // + // const gameConfig = Config.getGameConfig(); + // + // const slowestShipSpeed = Calculations.getSlowestShipSpeed(eventData.data.ships); + // + // // calculate duration of flight + // const timeOfFlight = Calculations.calculateTimeOfFlight( + // gameConfig.server.speed, + // eventData.speed, + // distance, + // slowestShipSpeed, + // ); + // + // const event: Event = new Event(); + // + // event.eventID = 0; + // event.ownerID = eventData.ownerID; + // event.mission = this.getMissionTypeID(eventData.mission); + // event.fleetlist = JSON.stringify(eventData.data.ships); + // event.startID = startPlanet.planetID; + // event.startType = this.getDestinationTypeByName(eventData.data.origin.type); + // event.startTime = Math.round(+new Date() / 1000); + // event.endID = destinationPlanet.planetID; + // event.endType = this.getDestinationTypeByName(eventData.data.destination.type); + // event.endTime = Math.round(event.startTime + timeOfFlight); + // event.loadedMetal = eventData.data.loadedRessources.metal; + // event.loadedCrystal = eventData.data.loadedRessources.crystal; + // event.loadedDeuterium = eventData.data.loadedRessources.deuterium; + // event.inQueue = false; + // event.returning = false; + // event.processed = false; + // + // await this.eventService.createNewEvent(event); + // + // // all done + // return response.status(Globals.StatusCodes.SUCCESS).json(event ?? {}); + // } catch (error) { + // this.logger.error(error, error.stack); + // + // return response.status(Globals.StatusCodes.SERVER_ERROR).json({ + // error: "There was an error while handling the request.", + // }); + // } + } - // all done - return response.status(Globals.Statuscode.SUCCESS).json({}); + @Post("/cancel") + @Security("jwt") + public async cancelEvent( + @Request() headers, + @Body() request: Event, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { + try { + return await this.eventService.cancel(request); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + // try { + // if (!InputValidator.isSet(request.body.eventID) || !InputValidator.isValidInt(request.body.eventID)) { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "Invalid parameter", + // }); + // } + // + // const userID = parseInt(request.userID, 10); + // const eventID = parseInt(request.body.eventID, 10); + // + // const event: Event = await this.eventService.getEventOfPlayer(userID, eventID); + // + // if (!InputValidator.isSet(event) || event.returning === true || event.inQueue === true) { + // return response.status(Globals.StatusCodes.BAD_REQUEST).json({ + // error: "The event does not exist or can't be canceled", + // }); + // } + // + // // (time passed from start until cancel) + (time now) + // event.endTime = Math.round(+new Date() / 1000) - event.startTime + Math.round(+new Date() / 1000); + // event.startTime = Math.round(+new Date() / 1000); + // + // await this.eventService.cancelEvent(event); + // + // // all done + // return response.status(Globals.StatusCodes.SUCCESS).json({}); + // } catch (error) { + // this.logger.error(error, error.stack); + // + // return response.status(Globals.StatusCodes.SERVER_ERROR).json({ + // error: "There was an error while handling the request.", + // }); + // } + } - /** - * Returns the ID of the destination-type - * @param type The type as a string (planet, moon or debris) - */ private getDestinationTypeByName(type: string): number { let typeID: number; switch (type) { @@ -228,10 +222,6 @@ export default class EventRouter { return typeID; } - /** - * Returns the ID of the mission-type - * @param mission The type as a string (transport, attack, ...) - */ private getMissionTypeID(mission: string): number { let missionTypeID: number; switch (mission) { diff --git a/src/routes/GalaxyRouter.spec.ts b/src/routes/GalaxyRouter.spec.ts index e1d872e..31f9bfc 100644 --- a/src/routes/GalaxyRouter.spec.ts +++ b/src/routes/GalaxyRouter.spec.ts @@ -3,14 +3,8 @@ import chaiHttp = require("chai-http"); import App from "../App"; import { Globals } from "../common/Globals"; -import SimpleLogger from "../loggers/SimpleLogger"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); - -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -21,7 +15,7 @@ let request = chai.request(app); describe("galaxyRouter", () => { before(() => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -39,7 +33,7 @@ describe("galaxyRouter", () => { .set("Authorization", authToken) .then(res => { expect(res.type).to.eql("application/json"); - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.body).to.have.lengthOf(2); expect(res.body[0].planetID).to.be.equals(61614); expect(res.body[0].posGalaxy).to.be.equals(7); @@ -55,7 +49,7 @@ describe("galaxyRouter", () => { .get("/v1/galaxy/-1/4") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.eql("Invalid parameter"); }); @@ -66,9 +60,9 @@ describe("galaxyRouter", () => { .get("/v1/galaxy/asdf/4") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.eql("Invalid parameter"); + expect(res.body.error).to.be.eql("Validation failed"); }); }); @@ -77,9 +71,9 @@ describe("galaxyRouter", () => { .get("/v1/galaxy/1/asdf") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.eql("Invalid parameter"); + expect(res.body.error).to.be.eql("Validation failed"); }); }); @@ -88,7 +82,7 @@ describe("galaxyRouter", () => { .get("/v1/galaxy/9/100") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body).to.be.eql([]); }); @@ -99,7 +93,7 @@ describe("galaxyRouter", () => { .get("/v1/galaxy/9999/4") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.eql("Invalid parameter"); }); @@ -110,7 +104,7 @@ describe("galaxyRouter", () => { .get("/v1/galaxy/4/-1") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.eql("Invalid parameter"); }); @@ -121,7 +115,7 @@ describe("galaxyRouter", () => { .get("/v1/galaxy/4/9999") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.eql("Invalid parameter"); }); diff --git a/src/routes/GalaxyRouter.ts b/src/routes/GalaxyRouter.ts index 6d62648..43ea114 100644 --- a/src/routes/GalaxyRouter.ts +++ b/src/routes/GalaxyRouter.ts @@ -1,70 +1,44 @@ -import { Response, Router } from "express"; import { Globals } from "../common/Globals"; import InputValidator from "../common/InputValidator"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import IGalaxyService from "../interfaces/IGalaxyService"; -import ILogger from "../interfaces/ILogger"; -/** - * Defines routes for galaxy-data - */ -export default class GalaxyRouter { - public router: Router = Router(); - - private logger: ILogger; - - private galaxyService: IGalaxyService; - - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.galaxyService = container.galaxyService; - this.router.get("/:posGalaxy/:posSystem", this.getGalaxyInformation); - - this.logger = logger; - } - - /** - * Returns a list of all galaxy-entries at the given position - * @param request - * @param response - * @param next - */ - public getGalaxyInformation = async (request: IAuthorizedRequest, response: Response) => { +import IGalaxyService from "../interfaces/services/IGalaxyService"; + +import { Controller, Get, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; +import FailureResponse from "../entities/responses/FailureResponse"; + +import GalaxyPositionInfo from "../units/GalaxyPositionInfo"; + +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("galaxy") +@Tags("Galaxy") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(GalaxyRouter) +export class GalaxyRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + @inject(TYPES.IGalaxyService) private galaxyService: IGalaxyService; + + @Get("/{posGalaxy}/{posSystem}") + @Security("jwt") + public async getGalaxyInformation( + posGalaxy: number, + posSystem: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - // validate parameters - if ( - !InputValidator.isSet(request.params.posGalaxy) || - !InputValidator.isValidInt(request.params.posGalaxy) || - !InputValidator.isSet(request.params.posSystem) || - !InputValidator.isValidInt(request.params.posSystem) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const posGalaxy = parseInt(request.params.posGalaxy, 10); - const posSystem = parseInt(request.params.posSystem, 10); - if (!InputValidator.isValidPosition(posGalaxy, posSystem)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse("Invalid parameter")); } - const galaxyData = await this.galaxyService.getGalaxyInfo(posGalaxy, posSystem); - - return response.status(Globals.Statuscode.SUCCESS).json(galaxyData ?? {}); + return await this.galaxyService.getPositionInfo(posGalaxy, posSystem); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/MessagesRouter.spec.ts b/src/routes/MessagesRouter.spec.ts index c755ae8..0fd8672 100644 --- a/src/routes/MessagesRouter.spec.ts +++ b/src/routes/MessagesRouter.spec.ts @@ -3,14 +3,8 @@ import chaiHttp = require("chai-http"); import App from "../App"; import { Globals } from "../common/Globals"; -import SimpleLogger from "../loggers/SimpleLogger"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); - -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -21,7 +15,7 @@ let request = chai.request(app); describe("messagesRouter", () => { before(() => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -35,10 +29,10 @@ describe("messagesRouter", () => { it("should return a list of messages", () => { return request - .get("/v1/messages/get") + .get("/v1/messages") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body[0].messageID).to.be.equals(5); expect(res.body[0].senderID).to.be.equals(1); @@ -51,10 +45,10 @@ describe("messagesRouter", () => { it("should return a specific message", () => { return request - .get("/v1/messages/get/5") + .get("/v1/messages/5") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.messageID).to.be.equals(5); expect(res.body.senderID).to.be.equals(1); @@ -67,23 +61,23 @@ describe("messagesRouter", () => { it("should fail (invalid message-id)", () => { return request - .get("/v1/messages/get/asdf") + .get("/v1/messages/asdf") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); it("should return nothing (message does not exist)", () => { return request - .get("/v1/messages/get/-1") + .get("/v1/messages/-1") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body).to.be.empty; + expect(res.body.error).to.be.equals("Message does not exist"); }); }); @@ -93,9 +87,8 @@ describe("messagesRouter", () => { .send({ receiverID: 48, subject: "Test", body: "Test" }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); - expect(res.type).to.eql("application/json"); expect(res.body).to.be.empty; + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); }); }); @@ -105,9 +98,9 @@ describe("messagesRouter", () => { .send({ subject: "Test", body: "Test" }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -117,9 +110,9 @@ describe("messagesRouter", () => { .send({ receiverID: 1, body: "Test" }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -129,9 +122,9 @@ describe("messagesRouter", () => { .send({ receiverID: 1, subject: "Test" }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -141,7 +134,7 @@ describe("messagesRouter", () => { .send({ receiverID: 91283917239, subject: "Test", body: "Test" }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.equals("The receiver does not exist"); }); @@ -150,11 +143,10 @@ describe("messagesRouter", () => { it("should delete a message", () => { return request .post("/v1/messages/delete") - .send({ messageID: 5 }) + .send({ messageID: 1 }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); - expect(res.type).to.eql("application/json"); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.body).to.be.empty; }); }); @@ -164,9 +156,9 @@ describe("messagesRouter", () => { .post("/v1/messages/delete") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -176,9 +168,9 @@ describe("messagesRouter", () => { .set("Authorization", authToken) .send({ messageID: "asdf" }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); }); diff --git a/src/routes/MessagesRouter.ts b/src/routes/MessagesRouter.ts index 1285dbd..d89ca57 100644 --- a/src/routes/MessagesRouter.ts +++ b/src/routes/MessagesRouter.ts @@ -1,158 +1,102 @@ -import { Response, Router } from "express"; import { Globals } from "../common/Globals"; import InputValidator from "../common/InputValidator"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import IMessageService from "../interfaces/IMessageService"; -import IUserService from "../interfaces/IUserService"; -import ILogger from "../interfaces/ILogger"; -/** - * Defines routes for message-sending and receiving - */ -export default class MessagesRouter { - public router: Router = Router(); - - private logger: ILogger; - - private userService: IUserService; - private messageService: IMessageService; - - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.userService = container.userService; - this.messageService = container.messageService; - this.router.get("/get", this.getAllMessages); - this.router.get("/get/:messageID", this.getMessageByID); - this.router.post("/delete", this.deleteMessage); - this.router.post("/send", this.sendMessage); - - this.logger = logger; - } - - /** - * Returns a list of all messages - * @param request - * @param response - * @param next - */ - public getAllMessages = async (request: IAuthorizedRequest, response: Response) => { +import IMessageService from "../interfaces/services/IMessageService"; +import IUserService from "../interfaces/services/IUserService"; + +import { Body, Controller, Get, Post, Request, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; +import SendMessageRequest from "../entities/requests/SendMessageRequest"; +import DeleteMessageRequest from "../entities/requests/DeleteMessageRequest"; +import { provide } from "inversify-binding-decorators"; +import FailureResponse from "../entities/responses/FailureResponse"; + +import Message from "../units/Message"; + +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("messages") +@Tags("Messages") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(MessagesRouter) +export class MessagesRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IUserService) private userService: IUserService; + @inject(TYPES.IMessageService) private messageService: IMessageService; + + @Get("/") + @Security("jwt") + public async getAllMessages( + @Request() headers, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - const userID = parseInt(request.userID, 10); - - const messages = await this.messageService.getAllMessages(userID); - - return response.status(Globals.Statuscode.SUCCESS).json(messages ?? {}); + return await this.messageService.getAll(headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Returns a specific message by its messageID - * @param request - * @param response - * @param next - */ - public getMessageByID = async (request: IAuthorizedRequest, response: Response) => { + @Get("/{messageID}") + @Security("jwt") + public async getMessageByID( + @Request() headers, + messageID: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(request.params.messageID) || !InputValidator.isValidInt(request.params.messageID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const messageID = parseInt(request.params.messageID, 10); - const message = await this.messageService.getMessageById(userID, messageID); - - return response.status(Globals.Statuscode.SUCCESS).json(message ?? {}); + return await this.messageService.getById(messageID, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Deletes a message by its messageID - * @param request - * @param response - * @param next - */ - public deleteMessage = async (request: IAuthorizedRequest, response: Response) => { + @Post("/send") + @Security("jwt") + public async sendMessage( + @Request() headers, + @Body() request: SendMessageRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(request.body.messageID) || !InputValidator.isValidInt(request.body.messageID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const messageID = parseInt(request.body.messageID, 10); + request.subject = InputValidator.sanitizeString(request.subject); + request.body = InputValidator.sanitizeString(request.body); - await this.messageService.deleteMessage(userID, messageID); + await this.messageService.send(request, headers.user.userID); - return response.status(Globals.Statuscode.SUCCESS).json({}); + return successResponse(Globals.StatusCodes.SUCCESS); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Sends a new message - * @param request - * @param response - * @param next - */ - public sendMessage = async (request: IAuthorizedRequest, response: Response) => { + @Post("/delete") + @Security("jwt") + public async deleteMessage( + @Request() headers, + @Body() request: DeleteMessageRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if ( - !InputValidator.isSet(request.body.receiverID) || - !InputValidator.isValidInt(request.body.receiverID) || - !InputValidator.isSet(request.body.subject) || - !InputValidator.isSet(request.body.body) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const receiverID = parseInt(request.body.receiverID, 10); - const subject = InputValidator.sanitizeString(request.body.subject); - const messageText = InputValidator.sanitizeString(request.body.body); + await this.messageService.delete(headers.user.userID, request.messageID); - const receiver = await this.userService.getUserById(receiverID); - - if (!InputValidator.isSet(receiver)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "The receiver does not exist", - }); - } - - await this.messageService.sendMessage(userID, receiverID, subject, messageText); - - return response.status(Globals.Statuscode.SUCCESS).json({}); + return successResponse(Globals.StatusCodes.SUCCESS); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/PlanetsRouter.spec.ts b/src/routes/PlanetsRouter.spec.ts index 4d759bf..16ce9f4 100644 --- a/src/routes/PlanetsRouter.spec.ts +++ b/src/routes/PlanetsRouter.spec.ts @@ -5,14 +5,15 @@ import App from "../App"; import { Globals } from "../common/Globals"; import Planet from "../units/Planet"; import User from "../units/User"; -import SimpleLogger from "../loggers/SimpleLogger"; +import { iocContainer } from "../ioc/inversify.config"; +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); +const planetRepository = iocContainer.get(TYPES.IPlanetRepository); +const userRepository = iocContainer.get(TYPES.IUserRepository); -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -25,10 +26,10 @@ describe("planetsRouter", () => { let planetBeforeTests: Planet; before(async () => { - authUserBeforeTests = await container.userService.getAuthenticatedUser(1); - planetBeforeTests = await container.planetService.getPlanet(1, 167546850, true); + authUserBeforeTests = await userRepository.getById(1); + planetBeforeTests = await planetRepository.getById(167546850); return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -36,8 +37,8 @@ describe("planetsRouter", () => { }); after(async () => { - await container.userService.updateUserData(authUserBeforeTests); - await container.planetService.updatePlanet(planetBeforeTests); + await userRepository.save(authUserBeforeTests); + await planetRepository.save(planetBeforeTests); }); beforeEach(function() { @@ -46,14 +47,16 @@ describe("planetsRouter", () => { }); it("should set the current planet", () => { + const planetID = 167546850; return request .post("/v1/user/currentplanet/set") - .send({ planetID: 167546850 }) + .send({ planetID: planetID }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); - expect(res.body).to.be.empty; + expect(res.body.currentPlanet).to.be.eql(planetID); + expect(res.body.password).to.be.undefined; }); }); @@ -62,8 +65,7 @@ describe("planetsRouter", () => { .post("/v1/user/currentplanet/set") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); }); }); @@ -74,56 +76,9 @@ describe("planetsRouter", () => { .send({ planetID: 1 }) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(res.body.error).to.be.equals("The player does not own the planet"); - expect(res.type).to.eql("application/json"); - }); - }); - - it("should return a list of planets", () => { - return request - .get("/v1/user/planetlist/") - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); - expect(res.type).to.eql("application/json"); - expect(res.body[0].planetID).to.be.equals(167546850); - expect(res.body[0].ownerID).to.be.equals(1); - expect(res.body[0].posGalaxy).to.be.equals(9); - expect(res.body[0].posSystem).to.be.equals(54); - expect(res.body[0].posPlanet).to.be.equals(1); - expect(res.body[0].metal).to.be.greaterThan(0); - expect(res.body[0].crystal).to.be.greaterThan(0); - expect(res.body[0].deuterium).to.be.greaterThan(0); - }); - }); - - it("should return a list of planets of an other user", () => { - return request - .get("/v1/user/planetlist/35") - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); - expect(res.type).to.eql("application/json"); - expect(res.body[0].planetID).to.be.equals(93133); - expect(res.body[0].ownerID).to.be.equals(35); - expect(res.body[0].posGalaxy).to.be.equals(4); - expect(res.body[0].posSystem).to.be.equals(71); - expect(res.body[0].posPlanet).to.be.equals(2); - expect(res.body[0].metal).to.be.equals(undefined); - expect(res.body[0].crystal).to.be.equals(undefined); - expect(res.body[0].deuterium).to.be.equals(undefined); - }); - }); - - it("should return nothing", () => { - return request - .get("/v1/user/planet/1234") - .set("Authorization", authToken) - .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(res.body.error).to.be.equals("Planet does not exist"); expect(res.type).to.eql("application/json"); - expect(res.body).to.be.empty; }); }); @@ -131,10 +86,10 @@ describe("planetsRouter", () => { const planetID = 167546850; return request - .get(`/v1/user/planet/${planetID}`) + .get(`/v1/planets/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.planetID).to.be.equals(planetID); expect(res.body.ownerID).to.be.equals(1); @@ -151,12 +106,12 @@ describe("planetsRouter", () => { const planetID = "asdf"; return request - .get(`/v1/user/planet/${planetID}`) + .get(`/v1/planets/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -167,8 +122,7 @@ describe("planetsRouter", () => { .get(`/v1/planets/movement/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); - expect(res.type).to.eql("application/json"); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); // TODO }); }); @@ -180,28 +134,28 @@ describe("planetsRouter", () => { .get(`/v1/planets/movement/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); it("should rename a planet", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planet: Planet = await planetRepository.getById(planetID); return request .post("/v1/planets/rename") - .send({ planetID, name: "FancyNewName" }) + .send({ planetID, newName: "FancyNewName" }) .set("Authorization", authToken) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.name).to.be.equals("FancyNewName"); // reset - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); }); }); @@ -213,9 +167,9 @@ describe("planetsRouter", () => { .send({ planetID }) .set("Authorization", authToken) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -225,23 +179,23 @@ describe("planetsRouter", () => { .send({ name: "NewName" }) .set("Authorization", authToken) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); - it("should fail (planerID not passed)", async () => { + it("should fail (name is too short)", async () => { const planetID = 167546850; return request .post("/v1/planets/rename") - .send({ planetID, name: "A" }) + .send({ planetID, newName: "A" }) .set("Authorization", authToken) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("New name is too short"); + expect(res.body.error.startsWith("Length of new name must be between")).to.be.true; }); }); @@ -252,9 +206,9 @@ describe("planetsRouter", () => { .get(`/v1/planets/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -265,7 +219,7 @@ describe("planetsRouter", () => { .get(`/v1/planets/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.planetID).to.be.equals(planetID); }); @@ -276,9 +230,9 @@ describe("planetsRouter", () => { .post("/v1/planets/destroy/") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -290,9 +244,9 @@ describe("planetsRouter", () => { .set("Authorization", authToken) .send({ planetID }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -301,14 +255,14 @@ describe("planetsRouter", () => { it("should delete the planet", async () => { const planetID = 167546999; - secondPlanetBackup = await container.planetService.getPlanet(1, planetID, true); + secondPlanetBackup = await planetRepository.getById(planetID); return request .post("/v1/planets/destroy/") .set("Authorization", authToken) .send({ planetID }) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); }); }); @@ -319,12 +273,12 @@ describe("planetsRouter", () => { return request .post("/v1/planets/destroy/") .set("Authorization", authToken) - .send({ planetID }) + .send({ planetID: planetID }) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.equals("The last planet cannot be destroyed"); - await container.planetService.createNewPlanet(secondPlanetBackup); + await planetRepository.create(secondPlanetBackup); }); }); }); diff --git a/src/routes/PlanetsRouter.ts b/src/routes/PlanetsRouter.ts index 6aed86d..df954cb 100644 --- a/src/routes/PlanetsRouter.ts +++ b/src/routes/PlanetsRouter.ts @@ -1,256 +1,112 @@ -import { Response, Router } from "express"; import { Globals } from "../common/Globals"; import InputValidator from "../common/InputValidator"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import IPlanetService from "../interfaces/IPlanetService"; -import Planet from "../units/Planet"; -import ILogger from "../interfaces/ILogger"; - -/** - * Defines routes for planet-data - */ -export default class PlanetsRouter { - public router: Router = Router(); - - private logger: ILogger; - - private planetService: IPlanetService; - - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.planetService = container.planetService; - - this.router.get("/movement/:planetID", this.getMovement); - this.router.post("/destroy/", this.destroyPlanet); - this.router.post("/rename/", this.renamePlanet); - this.router.get("/:planetID", this.getPlanetByID); - - this.logger = logger; - } - - /** - * Returns a list of all planets of a given authenticated user. - * This route returns sensible planet-data. - * @param request - * @param response - * @param next - */ - public getAllPlanets = async (request: IAuthorizedRequest, response: Response) => { - try { - const userID = parseInt(request.userID, 10); - - const planetList = await this.planetService.getAllPlanetsOfUser(userID, true); - - return response.status(Globals.Statuscode.SUCCESS).json(planetList ?? {}); - } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "There was an error while handling the request.", - }); - } - }; - /** - * Returns a list of all planets of a given user. - * This route returns only the basic planet-data. - * @param request - * @param response - * @param next - */ - public getAllPlanetsOfUser = async (request: IAuthorizedRequest, response: Response) => { - try { - const userID = parseInt(request.params.userID, 10); - - const planetList = await this.planetService.getAllPlanetsOfUser(userID); - - return response.status(Globals.Statuscode.SUCCESS).json(planetList ?? {}); - } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "There was an error while handling the request.", - }); - } - }; - - /** - * Returns a planet owned by the authenticated user - * @param request - * @param response - * @param next - */ - public getOwnPlanet = async (request: IAuthorizedRequest, response: Response) => { - try { - // validate parameters - if (!InputValidator.isSet(request.params.planetID) || !InputValidator.isValidInt(request.params.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.params.planetID, 10); - - const planet = await this.planetService.getPlanet(userID, planetID, true); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); - } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); - } - }; +import IPlanetService from "../interfaces/services/IPlanetService"; +import Planet from "../units/Planet"; - /** - * Returns a list of flights to and from the given planet - * @param request - * @param response - * @param next - */ - public getMovement = async (request: IAuthorizedRequest, response: Response) => { +import { Body, Controller, Get, Post, Request, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; + +import DestroyPlanetRequest from "../entities/requests/DestroyPlanetRequest"; +import RenamePlanetRequest from "../entities/requests/RenamePlanetRequest"; +import FailureResponse from "../entities/responses/FailureResponse"; + +import Event from "../units/Event"; + +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("planets") +@Tags("Planets") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(PlanetsRouter) +export class PlanetsRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + + @Get("/movement/{planetID}") + @Security("jwt") + public async getMovement( + @Request() headers, + planetID: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - // validate parameters - if (!InputValidator.isSet(request.params.planetID) || !InputValidator.isValidInt(request.params.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.params.planetID, 10); - - const movement = await this.planetService.getMovementOnPlanet(userID, planetID); - - return response.status(Globals.Statuscode.SUCCESS).json(movement ?? {}); + return successResponse( + Globals.StatusCodes.SUCCESS, + await this.planetService.getMovement(headers.user.userID, planetID), + ); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Destroys a given planet - * @param request - * @param response - * @param next - */ - public destroyPlanet = async (request: IAuthorizedRequest, response: Response) => { + @Post("/destroy") + @Security("jwt") + public async destroyPlanet( + @Request() headers, + @Body() request: DestroyPlanetRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - // validate parameters - if (!InputValidator.isSet(request.body.planetID) || !InputValidator.isValidInt(request.body.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - - const planetList = await this.planetService.getAllPlanetsOfUser(userID); - - if (planetList.length === 1) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "The last planet cannot be destroyed", - }); - } - - // TODO: if the deleted planet was the current planet -> set another one as current planet - await this.planetService.deletePlanet(userID, planetID); - - return response.status(Globals.Statuscode.SUCCESS).json({}); + return await this.planetService.destroy(request.planetID, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Renames a planet - * @param request - * @param response - * @param next - */ - public renamePlanet = async (request: IAuthorizedRequest, response: Response) => { + @Post("/rename") + @Security("jwt") + public async renamePlanet( + @Request() headers, + @Body() request: RenamePlanetRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - // validate parameters - if ( - !InputValidator.isSet(request.body.planetID) || - !InputValidator.isValidInt(request.body.planetID) || - !InputValidator.isSet(request.body.name) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } + request.newName = InputValidator.sanitizeString(request.newName); - const newName: string = InputValidator.sanitizeString(request.body.name); + // TODO: put into config + const minLength = 4; + const maxLength = 32; - // TODO: check max-length - if (newName.length <= 4) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "New name is too short", - }); + if (request.newName.length < minLength || request.newName.length > maxLength) { + return badRequestResponse( + Globals.StatusCodes.BAD_REQUEST, + new FailureResponse(`Length of new name must be between ${minLength} and ${maxLength}`), + ); } - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - - planet.name = newName; - - await this.planetService.updatePlanet(planet); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.planetService.rename(request, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Returns basic informations about a planet owned by the given user - * @param request - * @param response - * @param next - */ - public getPlanetByID = async (request: IAuthorizedRequest, response: Response) => { + @Get("/{planetID}") + @Security("jwt") + public async getPlanetByID( + @Request() headers, + planetID: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - // validate parameters - if (!InputValidator.isSet(request.params.planetID) || !InputValidator.isValidInt(request.params.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.params.planetID, 10); - - const planet: Planet = await this.planetService.getPlanet(userID, planetID); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.planetService.getById(planetID, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/ShipsRouter.spec.ts b/src/routes/ShipsRouter.spec.ts index 29ad844..1b4fe7b 100644 --- a/src/routes/ShipsRouter.spec.ts +++ b/src/routes/ShipsRouter.spec.ts @@ -4,14 +4,13 @@ import chaiHttp = require("chai-http"); import App from "../App"; import { Globals } from "../common/Globals"; import Planet from "../units/Planet"; -import SimpleLogger from "../loggers/SimpleLogger"; +import { iocContainer } from "../ioc/inversify.config"; +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); +const planetRepository = iocContainer.get(TYPES.IPlanetRepository); -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -23,9 +22,9 @@ describe("shipsRouter", () => { let planetBeforeTests: Planet; before(async () => { - planetBeforeTests = await container.planetService.getPlanet(1, 167546850, true); + planetBeforeTests = await planetRepository.getById(167546850); return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -33,7 +32,7 @@ describe("shipsRouter", () => { }); after(async () => { - await container.planetService.updatePlanet(planetBeforeTests); + await planetRepository.save(planetBeforeTests); }); beforeEach(function() { @@ -47,7 +46,7 @@ describe("shipsRouter", () => { .get(`/v1/ships/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.planetID).to.be.equals(planetID); }); @@ -59,9 +58,9 @@ describe("shipsRouter", () => { .get(`/v1/ships/${planetID}`) .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -69,11 +68,11 @@ describe("shipsRouter", () => { return request .post("/v1/ships/build") .set("Authorization", authToken) - .send({ planetID: "sadf", buildOrder: { 201: 3 } }) + .send({ planetID: "sadf", buildOrder: [{ unitID: 201, amount: 3 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -83,11 +82,11 @@ describe("shipsRouter", () => { return request .post("/v1/ships/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: { hallo: 3 } }) + .send({ planetID, buildOrder: [{ hallo: 3 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -97,11 +96,11 @@ describe("shipsRouter", () => { return request .post("/v1/ships/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: { 201: "asdf" } }) + .send({ planetID, buildOrder: [{ unitID: 201, amount: "asdf" }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("Invalid parameter"); + expect(res.body.error).to.be.equals("Validation failed"); }); }); @@ -112,9 +111,9 @@ describe("shipsRouter", () => { return request .post("/v1/ships/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: '{ "301": 3000 }' }) + .send({ planetID, buildOrder: [{ unitID: 301, amount: 3000 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.equals("Invalid parameter"); }); @@ -128,11 +127,11 @@ describe("shipsRouter", () => { return request .post("/v1/ships/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: '{ "201": 3000 }' }) + .send({ planetID, buildOrder: [{ unitID: 201, amount: 3000 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); - expect(res.body.error).to.be.equals("The player does not own the planet"); + expect(res.body.error).to.be.equals("Planet does not exist"); }); /* eslint-enable quotes */ }); @@ -140,27 +139,26 @@ describe("shipsRouter", () => { it("should fail (shipyard is upgrading)", async () => { const planetID = 167546850; - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planet: Planet = await planetRepository.getById(planetID); const valueBefore = planet.bHangarPlus; planet.bHangarPlus = true; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); /* eslint-disable quotes */ return request .post("/v1/ships/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: '{ "201": 3000 }' }) + .send({ planetID, buildOrder: [{ unitID: 201, amount: 3000 }] }) .then(async res => { - expect(res.status).to.be.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); expect(res.type).to.eql("application/json"); expect(res.body.error).to.be.equals("Shipyard is currently upgrading"); - // reset planet.bHangarPlus = valueBefore; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); }); /* eslint-enable quotes */ }); @@ -172,9 +170,9 @@ describe("shipsRouter", () => { return request .post("/v1/ships/build") .set("Authorization", authToken) - .send({ planetID, buildOrder: '{ "201": 4 }' }) + .send({ planetID, buildOrder: [{ unitID: 201, amount: 4 }] }) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.planetID).to.be.equals(planetID); const buildOrders = JSON.parse(res.body.bHangarQueue); diff --git a/src/routes/ShipsRouter.ts b/src/routes/ShipsRouter.ts index 4463f63..8f01c1e 100644 --- a/src/routes/ShipsRouter.ts +++ b/src/routes/ShipsRouter.ts @@ -1,224 +1,69 @@ -import { Response, Router } from "express"; -import Calculations from "../common/Calculations"; import { Globals } from "../common/Globals"; import InputValidator from "../common/InputValidator"; -import Queue from "../common/Queue"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import IBuildingService from "../interfaces/IBuildingService"; -import ICosts from "../interfaces/ICosts"; -import IPlanetService from "../interfaces/IPlanetService"; -import IShipService from "../interfaces/IShipService"; -import Buildings from "../units/Buildings"; -import Planet from "../units/Planet"; -import QueueItem from "../common/QueueItem"; -import ILogger from "../interfaces/ILogger"; - -/** - * Defines routes for ships-data - */ -export default class ShipsRouter { - public router: Router = Router(); - - private logger: ILogger; - - private planetService: IPlanetService; - private buildingService: IBuildingService; - private shipService: IShipService; - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.planetService = container.planetService; - this.buildingService = container.buildingService; - this.shipService = container.shipService; - - this.router.get("/:planetID", this.getAllShipsOnPlanet); - this.router.post("/build/", this.buildShips); - - this.logger = logger; - } +import IBuildingService from "../interfaces/services/IBuildingService"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import IShipService from "../interfaces/services/IShipService"; +import Planet from "../units/Planet"; - /** - * Returns a list of all ships on a given planet owned by the authenticated user - * @param request - * @param response - * @param next - */ - public getAllShipsOnPlanet = async (request: IAuthorizedRequest, response: Response) => { +import { Body, Controller, Get, Post, Request, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; + +import BuildShipsRequest from "../entities/requests/BuildShipsRequest"; +import FailureResponse from "../entities/responses/FailureResponse"; + +import Ships from "../units/Ships"; + +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("ships") +@Tags("Ships") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(ShipsRouter) +export class ShipsRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IBuildingService) private buildingService: IBuildingService; + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + @inject(TYPES.IShipService) private shipService: IShipService; + + @Get("/{planetID}") + @Security("jwt") + public async getAllShipsOnPlanet( + @Request() request, + planetID: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(request.params.planetID) || !InputValidator.isValidInt(request.params.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.params.planetID, 10); - - const ships = await this.shipService.getShips(userID, planetID); - - return response.status(Globals.Statuscode.SUCCESS).json(ships ?? {}); + return await this.shipService.getAll(request.user.userID, planetID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Starts a new build-order on the planet and appends it to the build-queue - * @param request - * @param response - * @param next - */ - public buildShips = async (request: IAuthorizedRequest, response: Response) => { + @Post("/build") + @Security("jwt") + public async buildShips( + @Request() headers, + @Body() request: BuildShipsRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if ( - !InputValidator.isSet(request.body.planetID) || - !InputValidator.isValidInt(request.body.planetID) || - !InputValidator.isSet(request.body.buildOrder) || - !InputValidator.isValidJson(request.body.buildOrder) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); + if (!InputValidator.isValidBuildOrder(request.buildOrder, Globals.UnitType.SHIP)) { + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse("Invalid parameter")); } - const buildOrders = JSON.parse(request.body.buildOrder); - - // validate build-order - if (!InputValidator.isValidBuildOrder(buildOrders, Globals.UnitType.SHIP)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - - const queue: Queue = new Queue(); - - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - const buildings: Buildings = await this.buildingService.getBuildings(planetID); - - if (planet === null) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "The player does not own the planet", - }); - } - - if (planet.bHangarPlus) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Shipyard is currently upgrading", - }); - } - - let metal = planet.metal; - let crystal = planet.crystal; - let deuterium = planet.deuterium; - - let stopProcessing = false; - let buildTime = 0; - - // TODO: put into seperate funciton (also reference this in defense-router) - for (const item in buildOrders) { - if (!buildOrders.hasOwnProperty(item)) { - continue; - } - - let count: number = buildOrders[item]; - const cost: ICosts = Calculations.getCosts(parseInt(item, 10), 1); - - // if the user has not enough ressources to fullfill the complete build-order - if (metal < cost.metal * count || crystal < cost.crystal * count || deuterium < cost.deuterium * count) { - let tempCount: number; - - if (cost.metal > 0) { - tempCount = metal / cost.metal; - - if (tempCount < count) { - count = tempCount; - } - } - - if (cost.crystal > 0) { - tempCount = crystal / cost.crystal; - - if (tempCount < count) { - count = tempCount; - } - } - - if (cost.deuterium > 0) { - tempCount = deuterium / cost.deuterium; - - if (tempCount < count) { - count = tempCount; - } - } - - // no need to further process the queue - stopProcessing = true; - } - - // build time in seconds - buildTime += - Calculations.calculateBuildTimeInSeconds( - cost.metal, - cost.crystal, - buildings.shipyard, - buildings.naniteFactory, - ) * Math.floor(count); - - queue.getQueue().push(new QueueItem(parseInt(item, 10), Math.floor(count))); - - metal -= cost.metal * count; - crystal -= cost.crystal * count; - deuterium -= cost.deuterium * count; - - if (stopProcessing) { - break; - } - } - - queue.setTimeRemaining(buildTime); - queue.setLastUpdateTime(Math.floor(Date.now() / 1000)); - - let oldBuildOrder; - - if (!InputValidator.isSet(planet.bHangarQueue)) { - planet.bHangarQueue = JSON.parse("[]"); - oldBuildOrder = planet.bHangarQueue; - } else { - oldBuildOrder = JSON.parse(planet.bHangarQueue); - } - - oldBuildOrder.push(queue); - - planet.bHangarQueue = JSON.stringify(oldBuildOrder); - - if (planet.bHangarStartTime === 0) { - planet.bHangarStartTime = Math.floor(Date.now() / 1000); - } - - planet.metal = metal; - planet.crystal = crystal; - planet.deuterium = deuterium; - - await this.planetService.updatePlanet(planet); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.shipService.processBuildOrder(request, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/TechsRouter.spec.ts b/src/routes/TechsRouter.spec.ts index 5bd6ad9..77eeecf 100644 --- a/src/routes/TechsRouter.spec.ts +++ b/src/routes/TechsRouter.spec.ts @@ -4,14 +4,12 @@ import chaiHttp = require("chai-http"); import App from "../App"; import { Globals } from "../common/Globals"; import Planet from "../units/Planet"; -import SimpleLogger from "../loggers/SimpleLogger"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); +import { iocContainer } from "../ioc/inversify.config"; +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -19,10 +17,12 @@ const expect = chai.expect; let authToken = ""; let request = chai.request(app); +const planetRepository = iocContainer.get(TYPES.IPlanetRepository); + describe("techsRouter", () => { before(() => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -36,10 +36,10 @@ describe("techsRouter", () => { it("should return a list of technologies", () => { return request - .get("/v1/techs/") + .get("/v1/technologies/") .set("Authorization", authToken) .then(res => { - expect(res.status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(res.type).to.eql("application/json"); expect(res.body.userID).to.be.equals(1); expect(res.body.gravitonTech).to.be.equals(1); @@ -50,23 +50,23 @@ describe("techsRouter", () => { const planetID = 167546850; return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) .send({ planetID }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.body.error).equals("Validation failed"); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("should fail (missing planetID-parameter)", () => { return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) .send({ techID: 1 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.body.error).equals("Validation failed"); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -74,12 +74,12 @@ describe("techsRouter", () => { const planetID = 167546850; return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) .send({ planetID, techID: -1 }) .then(res => { expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -87,23 +87,23 @@ describe("techsRouter", () => { const planetID = 167546850; return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) .send({ planetID, techID: 500 }) .then(res => { expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("should fail (player does not own planet)", () => { return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) .send({ planetID: 1234, techID: 101 }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.body.error).equals("Planet does not exist"); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -111,11 +111,11 @@ describe("techsRouter", () => { const planetID = 167546850; return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) .send({ planetID, techID: 101 }) .then(res => { - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); expect(res.body.planetID).to.be.equals(planetID); }); }); @@ -124,36 +124,36 @@ describe("techsRouter", () => { const planetID = 167546850; return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) - .send({ planetID: `${planetID}`, techID: "101" }) + .send({ planetID: `${planetID}`, techID: 101 }) .then(res => { expect(res.body.error).equals("Planet already has a build-job"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); }); }); it("try to start a tech-build-order while research-lab is upgrading", async () => { const planetID = 167546850; - const planetBackup: Planet = await container.planetService.getPlanet(1, planetID, true); - const planet: Planet = await container.planetService.getPlanet(1, planetID, true); + const planetBackup: Planet = await planetRepository.getById(planetID); + const planet: Planet = await planetRepository.getById(planetID); planet.bBuildingId = Globals.Buildings.RESEARCH_LAB; planet.bBuildingEndTime = 1; - await container.planetService.updatePlanet(planet); + await planetRepository.save(planet); return request - .post("/v1/techs/build") + .post("/v1/technologies/build") .set("Authorization", authToken) - .send({ planetID: `${planetID}`, techID: "101" }) + .send({ planetID: `${planetID}`, techID: 101 }) .then(async res => { expect(res.body.error).equals("Planet is upgrading the research-lab"); - expect(res.status).equals(Globals.Statuscode.BAD_REQUEST); + expect(res.status).equals(Globals.StatusCodes.BAD_REQUEST); // reset - await container.planetService.updatePlanet(planetBackup); + await planetRepository.save(planetBackup); }); }); @@ -161,12 +161,12 @@ describe("techsRouter", () => { const planetID = "test"; return request - .post("/v1/techs/cancel") + .post("/v1/technologies/cancel") .set("Authorization", authToken) - .send({ planetID: `${planetID}`, techID: "101" }) + .send({ planetID: `${planetID}` }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.body.error).equals("Validation failed"); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -174,11 +174,11 @@ describe("techsRouter", () => { const planetID = 167546850; return request - .post("/v1/techs/cancel") + .post("/v1/technologies/cancel") .set("Authorization", authToken) - .send({ planetID: `${planetID}`, techID: "101" }) + .send({ planetID: `${planetID}` }) .then(res => { - expect(res.status).to.equals(Globals.Statuscode.SUCCESS); + expect(res.status).to.equals(Globals.StatusCodes.SUCCESS); expect(res.body.planetID).to.equals(planetID); }); }); @@ -187,12 +187,12 @@ describe("techsRouter", () => { const planetID = 1234; return request - .post("/v1/techs/cancel") + .post("/v1/technologies/cancel") .set("Authorization", authToken) - .send({ planetID: `${planetID}`, techID: "1101" }) + .send({ planetID: `${planetID}` }) .then(res => { - expect(res.body.error).equals("Invalid parameter"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.body.error).equals("Planet does not exist"); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); @@ -200,12 +200,12 @@ describe("techsRouter", () => { const planetID = 167546850; return request - .post("/v1/techs/cancel") + .post("/v1/technologies/cancel") .set("Authorization", authToken) - .send({ planetID: `${planetID}`, techID: "1" }) + .send({ planetID: `${planetID}` }) .then(res => { - expect(res.body.error).equals("Planet has no build-job"); - expect(res.status).to.equals(Globals.Statuscode.BAD_REQUEST); + expect(res.body.error).equals("User is not currently researching"); + expect(res.status).to.equals(Globals.StatusCodes.BAD_REQUEST); }); }); }); diff --git a/src/routes/TechsRouter.ts b/src/routes/TechsRouter.ts index 21f3aae..044714b 100644 --- a/src/routes/TechsRouter.ts +++ b/src/routes/TechsRouter.ts @@ -1,267 +1,87 @@ -import { Response, Router } from "express"; -import Calculations from "../common/Calculations"; -import Config from "../common/Config"; import { Globals } from "../common/Globals"; -import InputValidator from "../common/InputValidator"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import IBuildingService from "../interfaces/IBuildingService"; -import ICosts from "../interfaces/ICosts"; -import IPlanetService from "../interfaces/IPlanetService"; -import ITechService from "../interfaces/ITechService"; -import Buildings from "../units/Buildings"; + +import IBuildingService from "../interfaces/services/IBuildingService"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import ITechService from "../interfaces/services/ITechService"; import Planet from "../units/Planet"; import Techs from "../units/Techs"; -import User from "../units/User"; -import IUserService from "../interfaces/IUserService"; -import ILogger from "../interfaces/ILogger"; - -/** - * Defines routes for technology-data - */ -export default class TechsRouter { - public router: Router = Router(); - - private logger: ILogger; - - private userService: IUserService; - private planetService: IPlanetService; - private buildingService: IBuildingService; - private techService: ITechService; - - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.userService = container.userService; - this.planetService = container.planetService; - this.buildingService = container.buildingService; - this.techService = container.techService; - - this.router.get("/", this.getTechs); - this.router.post("/build/", this.buildTech); - this.router.post("/cancel/", this.cancelTech); - - this.logger = logger; - } - /** - * Returns all technologies of a given user - * @param request - * @param response - * @param next - */ - public getTechs = async (request: IAuthorizedRequest, response: Response) => { +import IUserService from "../interfaces/services/IUserService"; + +import { Body, Controller, Get, Post, Request, Res, Route, Security, Tags, TsoaResponse } from "tsoa"; +import { provide } from "inversify-binding-decorators"; +import { inject } from "inversify"; +import TYPES from "../ioc/types"; + +import CancelTechRequest from "../entities/requests/CancelTechRequest"; +import BuildTechRequest from "../entities/requests/BuildTechRequest"; +import FailureResponse from "../entities/responses/FailureResponse"; + +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("technologies") +@Tags("Technologies") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(TechsRouter) +export class TechsRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IUserService) private userService: IUserService; + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + @inject(TYPES.IBuildingService) private buildingService: IBuildingService; + @inject(TYPES.ITechService) private techService: ITechService; + + @Get("/") + @Security("jwt") + public async getTechs( + @Request() headers, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - const userID = parseInt(request.userID, 10); - - const techs: Techs = await this.techService.getTechs(userID); - - return response.status(Globals.Statuscode.SUCCESS).json(techs ?? {}); + return await this.techService.getAll(headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Cancels a currently researching technology - * @param request - * @param response - * @param next - */ - public cancelTech = async (request: IAuthorizedRequest, response: Response) => { + @Post("/build") + @Security("jwt") + public async buildTech( + @Request() headers, + @Body() request: BuildTechRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if (!InputValidator.isSet(request.body.planetID) || !InputValidator.isValidInt(request.body.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - const techs: Techs = await this.techService.getTechs(userID); - const user: User = await this.userService.getAuthenticatedUser(userID); - - // player does not own the planet - if (!InputValidator.isSet(planet)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - // 1. check if there is already a build-job on the planet - if (user.bTechID === 0 && user.bTechEndTime === 0) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Planet has no build-job", - }); + if (request.techID < Globals.MIN_TECHNOLOGY_ID || request.techID > Globals.MAX_TECHNOLOGY_ID) { + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse("Invalid parameter")); } - const techKey = Globals.UnitNames[user.bTechID]; - - const currentLevel = techs[techKey]; - - const cost: ICosts = Calculations.getCosts(user.bTechID, currentLevel); - - planet.metal += cost.metal; - planet.crystal += cost.crystal; - planet.deuterium += cost.deuterium; - user.bTechID = 0; - user.bTechEndTime = 0; - - await this.planetService.updatePlanet(planet); - await this.userService.updateUserData(user); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.techService.build(request, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } - /** - * Starts researching a new technology - * @param request - * @param response - * @param next - */ - public buildTech = async (request: IAuthorizedRequest, response: Response) => { + @Post("/cancel") + @Security("jwt") + public async cancelTech( + @Request() headers, + @Body() request: CancelTechRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { try { - if ( - !InputValidator.isSet(request.body.planetID) || - !InputValidator.isValidInt(request.body.planetID) || - !InputValidator.isSet(request.body.techID) || - !InputValidator.isValidInt(request.body.techID) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - const techID = parseInt(request.body.techID, 10); - - if (request.body.techID < Globals.MIN_TECHNOLOGY_ID || request.body.techID > Globals.MAX_TECHNOLOGY_ID) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const planet: Planet = await this.planetService.getPlanet(userID, planetID, true); - const buildings: Buildings = await this.buildingService.getBuildings(planetID); - const techs: Techs = await this.techService.getTechs(userID); - const user: User = await this.userService.getAuthenticatedUser(userID); - - if (!InputValidator.isSet(planet)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - if (planet.isUpgradingResearchLab()) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Planet is upgrading the research-lab", - }); - } - - // 1. check if there is already a build-job on the planet - if (user.isResearching()) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Planet already has a build-job", - }); - } - - // can't build research lab while they are researching... poor scientists :( - // if(request.body.techID === Globals.Buildings.RESEARCH_LAB && - // (planet.bTechID > 0 || planet.bTechEndTime > 0)) { - // - // response.status(Globals.Statuscode.SUCCESS).json({ - // status: Globals.Statuscode.SUCCESS, - // error: "Can't build this building while it is in use", - // data: {} - // }); - // - // return; - // } - - // 2. check, if requirements are met - const requirements = Config.getGameConfig().units.technologies.find(r => r.unitID === techID).requirements; - - // building has requirements - if (requirements !== undefined) { - let requirementsMet = true; - - for (const reqID in requirements) { - if (requirements.hasOwnProperty(reqID)) { - const reqLevel = requirements[reqID]; - const key = Globals.UnitNames[reqID]; - - if (techs[key] < reqLevel) { - requirementsMet = false; - break; - } - } else { - // TODO: throw a meaningful error - throw Error(); - } - } - - if (!requirementsMet) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Requirements are not met", - }); - } - } - - // 3. check if there are enough resources on the planet for the building to be built - const buildingKey = Globals.UnitNames[techID]; - - const currentLevel = techs[buildingKey]; - - const cost = Calculations.getCosts(techID, currentLevel); - - if ( - planet.metal < cost.metal || - planet.crystal < cost.crystal || - planet.deuterium < cost.deuterium || - planet.energyMax < cost.energy - ) { - return response.status(Globals.Statuscode.SUCCESS).json({ - error: "Not enough resources", - }); - } - - // 4. start the build-job - const buildTime = Calculations.calculateResearchTimeInSeconds(cost.metal, cost.crystal, buildings.researchLab); - - const endTime = Math.round(+new Date() / 1000) + buildTime; - - planet.metal = planet.metal - cost.metal; - planet.crystal = planet.crystal - cost.crystal; - planet.deuterium = planet.deuterium - cost.deuterium; - user.bTechID = techID; - user.bTechEndTime = endTime; - - await this.planetService.updatePlanet(planet); - await this.userService.updateUserData(user); - - return response.status(Globals.Statuscode.SUCCESS).json(planet ?? {}); + return await this.techService.cancel(request, headers.user.userID); } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); } - }; + } } diff --git a/src/routes/UserRouter.ts b/src/routes/UserRouter.ts new file mode 100644 index 0000000..d12f89b --- /dev/null +++ b/src/routes/UserRouter.ts @@ -0,0 +1,153 @@ +import { Globals } from "../common/Globals"; +import User from "../units/User"; +import InputValidator from "../common/InputValidator"; + +import FailureResponse from "../entities/responses/FailureResponse"; + +import IBuildingService from "../interfaces/services/IBuildingService"; +import IDefenseService from "../interfaces/services/IDefenseService"; +import IGalaxyService from "../interfaces/services/IGalaxyService"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import IShipService from "../interfaces/services/IShipService"; +import ITechService from "../interfaces/services/ITechService"; +import IUserService from "../interfaces/services/IUserService"; + +import CreateUserRequest from "../entities/requests/CreateUserRequest"; +import UpdateUserRequest from "../entities/requests/UpdateUserRequest"; +import SetCurrentPlanetRequest from "../entities/requests/SetCurrentPlanetRequest"; + +import { inject } from "inversify"; +import TYPES from "../ioc/types"; +import { provide } from "inversify-binding-decorators"; + +import { Route, Get, Tags, Controller, Security, Request, Post, Body, Res, TsoaResponse } from "tsoa"; + +import AuthSuccessResponse from "../entities/responses/AuthSuccessResponse"; +import Planet from "../units/Planet"; + +import IErrorHandler from "../interfaces/IErrorHandler"; + +@Route("user") +@Tags("UserData") +// eslint-disable-next-line @typescript-eslint/no-use-before-define +@provide(UserRouter) +export class UserRouter extends Controller { + @inject(TYPES.IErrorHandler) private errorHandler: IErrorHandler; + + @inject(TYPES.IUserService) private userService: IUserService; + @inject(TYPES.IGalaxyService) private galaxyService: IGalaxyService; + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + @inject(TYPES.IBuildingService) private buildingService: IBuildingService; + @inject(TYPES.IDefenseService) private defenseService: IDefenseService; + @inject(TYPES.IShipService) private shipService: IShipService; + @inject(TYPES.ITechService) private techService: ITechService; + + @Get("/") + @Security("jwt") + public async getUserSelf( + @Request() request, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { + try { + return await this.userService.getAuthenticatedUser(request.user.userID); + } catch (error) { + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); + } + } + + @Post("/create") + public async createUser( + @Body() request: CreateUserRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { + try { + if ( + !InputValidator.isSet(request.username) || + !InputValidator.isSet(request.password) || + !InputValidator.isSet(request.email) + ) { + return badRequestResponse(Globals.StatusCodes.BAD_REQUEST, new FailureResponse("Invalid parameter")); + } + + request.username = InputValidator.sanitizeString(request.username); + request.password = InputValidator.sanitizeString(request.password); + request.email = InputValidator.sanitizeString(request.email); + + return await this.userService.create(request); + } catch (error) { + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); + } + } + + @Post("/update") + @Security("jwt") + public async updateUser( + @Request() headers, + @Body() requestModel: UpdateUserRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { + try { + return await this.userService.update(requestModel, headers.user.userID); + } catch (error) { + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); + } + } + + @Get("/planetList") + @Security("jwt") + public async getAllPlanetsOfUser( + @Request() request, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { + try { + return await this.planetService.getAll(request.user.userID); + } catch (error) { + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); + } + } + + @Post("/currentplanet/set") + @Security("jwt") + public async setCurrentPlanet( + @Request() headers, + @Body() request: SetCurrentPlanetRequest, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { + try { + return await this.userService.setCurrentPlanet(request, headers.user.userID); + } catch (error) { + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); + } + } + + @Get("/{userID}") + @Security("jwt") + public async getUserByID( + userID: number, + @Res() successResponse: TsoaResponse, + @Res() badRequestResponse: TsoaResponse, + @Res() unauthorizedResponse: TsoaResponse, + @Res() serverErrorResponse: TsoaResponse, + ): Promise { + try { + return await this.userService.getOtherUser(userID); + } catch (error) { + return this.errorHandler.handle(error, badRequestResponse, unauthorizedResponse, serverErrorResponse); + } + } +} diff --git a/src/routes/UsersRouter.spec.ts b/src/routes/UsersRouter.spec.ts index e988806..cb377c7 100644 --- a/src/routes/UsersRouter.spec.ts +++ b/src/routes/UsersRouter.spec.ts @@ -3,14 +3,9 @@ import * as chai from "chai"; import App from "../App"; import { Globals } from "../common/Globals"; import chaiHttp = require("chai-http"); -import SimpleLogger from "../loggers/SimpleLogger"; +import UpdateUserRequest from "../entities/requests/UpdateUserRequest"; -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); - -const container = createContainer(); - -const app = new App(container, new SimpleLogger()).express; +const app = new App().express; chai.use(chaiHttp); const expect = chai.expect; @@ -21,7 +16,7 @@ describe("User Routes", () => { before(() => { return request - .post("/v1/auth/login") + .post("/v1/login") .send({ email: "user_1501005189510@test.com", password: "admin" }) .then(res => { authToken = res.body.token; @@ -37,23 +32,14 @@ describe("User Routes", () => { const user = { username: "IDoNotExistYet", password: "test", - email: "iamnotareal@email.com", + email: "iamnotarealaddress@email.com", }; - container.galaxyService.getFreePosition = function() { - return { - posGalaxy: 1, - posSystem: 1, - posPlanet: 1, - type: 1, - }; - }; - - const { type, status, body } = await request.post("/v1/users/create/").send(user); + const { type, status, body } = await request.post("/v1/user/create/").send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.SUCCESS); - expect(body).to.have.keys("userID", "token"); + expect(status).to.be.equals(Globals.StatusCodes.SUCCESS); + expect(body).to.have.keys("token"); expect(body.token.length).to.be.above(120); }); @@ -64,11 +50,10 @@ describe("User Routes", () => { email: "test@test.com", }; - const { type, status, body } = await request.post("/v1/users/create/").send(user); + const { status, body } = await request.post("/v1/user/create/").send(user); - expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("There was an error while handling the request: Username is already taken"); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).to.be.equals("Username is already taken"); }); it("should fail (email already taken)", async () => { @@ -78,11 +63,11 @@ describe("User Routes", () => { email: "L17@WEC.test", }; - const { type, status, body } = await request.post("/v1/users/create/").send(user); + const { type, status, body } = await request.post("/v1/user/create/").send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("There was an error while handling the request: Email is already taken"); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).to.be.equals("Email is already taken"); }); it("user-creation should fail (invalid parameters)", async () => { @@ -91,11 +76,11 @@ describe("User Routes", () => { password: "test", }; - const { type, status, body } = await request.post("/v1/users/create/").send(user); + const { type, status, body } = await request.post("/v1/user/create/").send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("Invalid parameter"); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).to.be.equals("Validation failed"); }); it("user-creation should fail (invalid parameters)", async () => { @@ -104,11 +89,11 @@ describe("User Routes", () => { email: "iamnotareal@email.com", }; - const { type, status, body } = await request.post("/v1/users/create/").send(user); + const { type, status, body } = await request.post("/v1/user/create/").send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("Invalid parameter"); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).to.be.equals("Validation failed"); }); it("user-creation should fail (invalid parameters)", async () => { @@ -117,26 +102,26 @@ describe("User Routes", () => { email: "iamnotareal@email.com", }; - const { type, status, body } = await request.post("/v1/users/create/").send(user); + const { type, status, body } = await request.post("/v1/user/create/").send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("Invalid parameter"); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).to.be.equals("Validation failed"); }); it("user-creation should fail (no data sent)", async () => { - const { type, status, body } = await request.post("/v1/users/create/"); + const { type, status, body } = await request.post("/v1/user/create/"); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("Invalid parameter"); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).to.be.equals("Validation failed"); }); it("should return the user", async () => { const { type, status, body } = await request.get("/v1/user/").set("Authorization", authToken); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(body.userID).to.be.equals(1); expect(body.username).to.not.be.equals(null); expect(body.email).to.not.be.equals(null); @@ -145,10 +130,9 @@ describe("User Routes", () => { }); it("should return a user", async () => { - const { type, status, body } = await request.get("/v1/users/41").set("Authorization", authToken); + const { status, body } = await request.get("/v1/user/41").set("Authorization", authToken); - expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(body.userID).to.be.equals(41); expect(body.username).to.not.be.equals(null); expect(body.email).to.be.equals(undefined); @@ -157,24 +141,24 @@ describe("User Routes", () => { }); it("should fail (invalid userID)", async () => { - const { type, status, body } = await request.get("/v1/users/asdf").set("Authorization", authToken); + const { type, status, body } = await request.get("/v1/user/asdf").set("Authorization", authToken); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).to.be.equals("Validation failed"); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("Invalid parameter"); }); it("should return nothing (user does not exist)", async () => { - const { type, status } = await request.get("/v1/users/2").set("Authorization", authToken); + const { type, status } = await request.get("/v1/user/2").set("Authorization", authToken); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); }); it("should update the user", async () => { const user = { username: "testuser1234", - }; + } as UpdateUserRequest; const { type, status, body } = await request .post("/v1/user/update") @@ -182,7 +166,7 @@ describe("User Routes", () => { .send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(body.userID).to.be.equals(1); expect(body.username).to.be.equals("testuser1234"); @@ -195,11 +179,10 @@ describe("User Routes", () => { }); it("update should fail (no data sent)", async () => { - const { type, status, body } = await request.post("/v1/user/update").set("Authorization", authToken); + const { type, status } = await request.post("/v1/user/update").set("Authorization", authToken); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).to.be.equals("No parameters were passed"); + expect(status).to.be.equals(Globals.StatusCodes.SUCCESS); }); it("update should fail (username already taken)", async () => { @@ -213,8 +196,8 @@ describe("User Routes", () => { .send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).contain("There was an error while handling the request: "); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).equals("Username already taken"); }); it("update should fail (email already taken)", async () => { @@ -228,8 +211,8 @@ describe("User Routes", () => { .send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.BAD_REQUEST); - expect(body.error).contain("There was an error while handling the request: "); + expect(status).to.be.equals(Globals.StatusCodes.BAD_REQUEST); + expect(body.error).contains("Email already taken"); }); it("update password", async () => { @@ -243,8 +226,26 @@ describe("User Routes", () => { .send(user); expect(type).to.be.equals("application/json"); - expect(status).to.be.equals(Globals.Statuscode.SUCCESS); + expect(status).to.be.equals(Globals.StatusCodes.SUCCESS); expect(body.userID).to.be.equals(1); expect(body.username).to.be.equals("admin"); }); + + it("should return a list of planets", () => { + return request + .get("/v1/user/planetlist") + .set("Authorization", authToken) + .then(res => { + expect(res.status).to.be.equals(Globals.StatusCodes.SUCCESS); + expect(res.type).to.eql("application/json"); + expect(res.body[0].planetID).to.be.equals(167546850); + expect(res.body[0].ownerID).to.be.equals(1); + expect(res.body[0].posGalaxy).to.be.equals(9); + expect(res.body[0].posSystem).to.be.equals(54); + expect(res.body[0].posPlanet).to.be.equals(1); + expect(res.body[0].metal).to.be.greaterThan(0); + expect(res.body[0].crystal).to.be.greaterThan(0); + expect(res.body[0].deuterium).to.be.greaterThan(0); + }); + }); }); diff --git a/src/routes/UsersRouter.ts b/src/routes/UsersRouter.ts deleted file mode 100644 index bf252b0..0000000 --- a/src/routes/UsersRouter.ts +++ /dev/null @@ -1,410 +0,0 @@ -import { Request, Response, Router } from "express"; -import Config from "../common/Config"; -import Database from "../common/Database"; -import DuplicateRecordException from "../exceptions/DuplicateRecordException"; -import { Globals } from "../common/Globals"; -import Encryption from "../common/Encryption"; -import InputValidator from "../common/InputValidator"; -import IAuthorizedRequest from "../interfaces/IAuthorizedRequest"; -import IBuildingService from "../interfaces/IBuildingService"; -import IDefenseService from "../interfaces/IDefenseService"; -import IGalaxyService from "../interfaces/IGalaxyService"; -import IGameConfig from "../interfaces/IGameConfig"; -import IPlanetService from "../interfaces/IPlanetService"; -import IShipService from "../interfaces/IShipService"; -import ITechService from "../interfaces/ITechService"; -import IUserService from "../interfaces/IUserService"; -import Planet from "../units/Planet"; -import User from "../units/User"; -import PlanetsRouter from "./PlanetsRouter"; -import JwtHelper from "../common/JwtHelper"; -import PlanetType = Globals.PlanetType; -import ILogger from "../interfaces/ILogger"; - -/** - * Defines routes for user-data - */ -export default class UsersRouter { - public router: Router = Router(); - - private logger: ILogger; - - private userService: IUserService; - private galaxyService: IGalaxyService; - private planetService: IPlanetService; - private buildingService: IBuildingService; - private defenseService: IDefenseService; - private shipService: IShipService; - private techService: ITechService; - - /** - * Registers the routes and needed services - * @param container the IoC-container with registered services - * @param logger Instance of an ILogger-object - */ - public constructor(container, logger: ILogger) { - this.userService = container.userService; - this.galaxyService = container.galaxyService; - this.planetService = container.planetService; - this.buildingService = container.buildingService; - this.defenseService = container.defenseService; - this.shipService = container.shipService; - this.techService = container.techService; - - // /user/create/ - this.router.post("/create", this.createUser); - - // /user/update - this.router.post("/update", this.updateUser); - - // /user/planet/:planetID - this.router.get("/planet/:planetID", new PlanetsRouter(container, logger).getOwnPlanet); - - // /user/planetlist/ - this.router.get("/planetlist/", new PlanetsRouter(container, logger).getAllPlanets); - - // /users/planetlist/:userID - this.router.get("/planetlist/:userID", new PlanetsRouter(container, logger).getAllPlanetsOfUser); - - // /user/currentplanet/set/:planetID - this.router.post("/currentplanet/set", this.setCurrentPlanet); - - // /users/:userID - this.router.get("/:userID", this.getUserByID); - - // /user - this.router.get("/", this.getUserSelf); - - this.logger = logger; - } - - /** - * Returns sensible information about the currently authenticated user - * @param request - * @param response - * @param next - */ - public getUserSelf = async (request: IAuthorizedRequest, response: Response) => { - try { - // validate parameters - if (!InputValidator.isSet(request.userID) || !InputValidator.isValidInt(request.userID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const data = await this.userService.getAuthenticatedUser(parseInt(request.userID, 10)); - - return response.status(Globals.Statuscode.SUCCESS).json(data ?? {}); - } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); - } - }; - - /** - * Returns basic information about a user given its userID - * @param request - * @param response - * @param next - */ - public getUserByID = async (request: IAuthorizedRequest, response: Response) => { - try { - if (!InputValidator.isSet(request.params.userID) || !InputValidator.isValidInt(request.params.userID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID: number = parseInt(request.params.userID, 10); - - const user = await this.userService.getUserById(userID); - - return response.status(Globals.Statuscode.SUCCESS).json(user ?? {}); - } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); - } - }; - - /** - * Creates a new user with homeplanet - * @param request - * @param response - * @param next - */ - public createUser = async (request: Request, response: Response) => { - if ( - !InputValidator.isSet(request.body.username) || - !InputValidator.isSet(request.body.password) || - !InputValidator.isSet(request.body.email) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const gameConfig: IGameConfig = Config.getGameConfig(); - - const username: string = InputValidator.sanitizeString(request.body.username); - const password: string = InputValidator.sanitizeString(request.body.password); - const email: string = InputValidator.sanitizeString(request.body.email); - - const hashedPassword = await Encryption.hash(password); - - const connection = await Database.getConnectionPool().getConnection(); - - const newUser: User = new User(); - const newPlanet: Planet = new Planet(); - - try { - await connection.beginTransaction(); - - const data = await this.userService.checkIfNameOrMailIsTaken(username, email); - - if (data.username_taken === 1) { - throw new DuplicateRecordException("Username is already taken"); - } - - if (data.email_taken === 1) { - throw new DuplicateRecordException("Email is already taken"); - } - - this.logger.info("Getting a new userID"); - - newUser.username = username; - newUser.email = email; - - const userID = await this.userService.getNewId(); - - newUser.userID = userID; - newPlanet.ownerID = userID; - newUser.password = hashedPassword; - newPlanet.planetType = PlanetType.PLANET; - - this.logger.info("Getting a new planetID"); - - const planetID = await this.planetService.getNewId(); - - newUser.currentPlanet = planetID; - newPlanet.planetID = planetID; - - this.logger.info("Finding free position for new planet"); - - const galaxyData = await this.galaxyService.getFreePosition( - gameConfig.server.limits.galaxy.max, - gameConfig.server.limits.system.max, - gameConfig.server.startPlanet.minPlanetPos, - gameConfig.server.startPlanet.maxPlanetPos, - ); - - newPlanet.posGalaxy = galaxyData.posGalaxy; - newPlanet.posSystem = galaxyData.posSystem; - newPlanet.posPlanet = galaxyData.posPlanet; - - this.logger.info("Creating a new user"); - - await this.userService.createNewUser(newUser, connection); - - this.logger.info("Creating a new planet"); - - newPlanet.name = gameConfig.server.startPlanet.name; - newPlanet.lastUpdate = Math.floor(Date.now() / 1000); - newPlanet.diameter = gameConfig.server.startPlanet.diameter; - newPlanet.fieldsMax = gameConfig.server.startPlanet.fields; - newPlanet.metal = gameConfig.server.startPlanet.resources.metal; - newPlanet.crystal = gameConfig.server.startPlanet.resources.crystal; - newPlanet.deuterium = gameConfig.server.startPlanet.resources.deuterium; - - switch (true) { - case newPlanet.posPlanet <= 5: { - newPlanet.tempMin = Math.random() * (130 - 40) + 40; - newPlanet.tempMax = Math.random() * (150 - 240) + 240; - - const images: string[] = ["desert", "dry"]; - - newPlanet.image = - images[Math.floor(Math.random() * images.length)] + Math.round(Math.random() * (10 - 1) + 1) + ".png"; - - break; - } - case newPlanet.posPlanet <= 10: { - newPlanet.tempMin = Math.random() * (130 - 40) + 40; - newPlanet.tempMax = Math.random() * (150 - 240) + 240; - - const images: string[] = ["normal", "jungle", "gas"]; - - newPlanet.image = - images[Math.floor(Math.random() * images.length)] + Math.round(Math.random() * (10 - 1) + 1) + ".png"; - - break; - } - case newPlanet.posPlanet <= 15: { - newPlanet.tempMin = Math.random() * (130 - 40) + 40; - newPlanet.tempMax = Math.random() * (150 - 240) + 240; - - const images: string[] = ["ice", "water"]; - - newPlanet.image = - images[Math.floor(Math.random() * images.length)] + Math.round(Math.random() * (10 - 1) + 1) + ".png"; - } - } - - await this.planetService.createNewPlanet(newPlanet, connection); - - this.logger.info("Creating entry in buildings-table"); - - await this.buildingService.createBuildingsRow(newPlanet.planetID, connection); - - this.logger.info("Creating entry in defenses-table"); - - await this.defenseService.createDefenseRow(newPlanet.planetID, connection); - - this.logger.info("Creating entry in ships-table"); - - await this.shipService.createShipsRow(newPlanet.planetID, connection); - - this.logger.info("Creating entry in galaxy-table"); - - await this.galaxyService.createGalaxyRow( - newPlanet.planetID, - newPlanet.posGalaxy, - newPlanet.posSystem, - newPlanet.posPlanet, - connection, - ); - - this.logger.info("Creating entry in techs-table"); - - await this.techService.createTechRow(newUser.userID, connection); - - connection.commit(); - - this.logger.info("Transaction complete"); - - await connection.commit(); - } catch (error) { - await connection.rollback(); - this.logger.error(error, error); - - if (error instanceof DuplicateRecordException || error.message.includes("Duplicate entry")) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: `There was an error while handling the request: ${error.message}`, - }); - } - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); - } finally { - await connection.release(); - } - - return response.status(Globals.Statuscode.SUCCESS).json({ - userID: newUser.userID, - token: JwtHelper.generateToken(newUser.userID), - }); - }; - - /** - * Updates a user - * @param request - * @param response - * @param next - */ - public updateUser = async (request: IAuthorizedRequest, response: Response) => { - try { - // if no parameters are set - if ( - !InputValidator.isSet(request.body.username) && - !InputValidator.isSet(request.body.password) && - !InputValidator.isSet(request.body.email) - ) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "No parameters were passed", - }); - } - - const user: User = await this.userService.getAuthenticatedUser(parseInt(request.userID, 10)); - - if (InputValidator.isSet(request.body.username)) { - // TODO: Check if username already exists - user.username = InputValidator.sanitizeString(request.body.username); - } - - if (InputValidator.isSet(request.body.password)) { - const password = InputValidator.sanitizeString(request.body.password); - - user.password = await Encryption.hash(password); - } - - if (InputValidator.isSet(request.body.email)) { - user.email = InputValidator.sanitizeString(request.body.email); - } - - await this.userService.updateUserData(user); - - return response.status(Globals.Statuscode.SUCCESS).json(user ?? {}); - } catch (error) { - this.logger.error(error, error.stack); - - if (error instanceof DuplicateRecordException || error.message.includes("Duplicate entry")) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: `There was an error while handling the request: ${error.message}`, - }); - } else { - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); - } - } - }; - - /** - * Sets the current planet for a user - * @param request - * @param response - * @param next - */ - public setCurrentPlanet = async (request: IAuthorizedRequest, response: Response) => { - try { - // validate parameters - if (!InputValidator.isSet(request.body.planetID) || !InputValidator.isValidInt(request.body.planetID)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "Invalid parameter", - }); - } - - const userID = parseInt(request.userID, 10); - const planetID = parseInt(request.body.planetID, 10); - - const planet: Planet = await this.planetService.getPlanet(userID, planetID); - - if (!InputValidator.isSet(planet)) { - return response.status(Globals.Statuscode.BAD_REQUEST).json({ - error: "The player does not own the planet", - }); - } - - const user: User = await this.userService.getUserById(userID); - - user.currentPlanet = planetID; - - await this.userService.updateUserData(user); - - return response.status(Globals.Statuscode.SUCCESS).json({}); - } catch (error) { - this.logger.error(error, error.stack); - - return response.status(Globals.Statuscode.SERVER_ERROR).json({ - error: "There was an error while handling the request.", - }); - } - }; -} diff --git a/src/services/AuthService.spec.ts b/src/services/AuthService.spec.ts new file mode 100644 index 0000000..a78d350 --- /dev/null +++ b/src/services/AuthService.spec.ts @@ -0,0 +1,55 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +import AuthService from "./AuthService"; +import IUserService from "../interfaces/services/IUserService"; +import { anyString, instance, mock, when } from "ts-mockito"; +import User from "../units/User"; +import UserService from "./UserService"; + +import UnauthorizedException from "../exceptions/UnauthorizedException"; + +const chai = require("chai"); +const chaiAsPromised = require("chai-as-promised"); +const expect = chai.expect; +chai.use(chaiAsPromised); + +describe("AuthService", () => { + it("should return a token", async () => { + const userServiceMock: IUserService = mock(UserService); + + when(userServiceMock.getUserForAuthentication(anyString())).thenResolve({ + username: "Admin", + password: "$2b$10$l37H7il.konAYEMjLw.qWudFesOPSaKjtGj6VGw11Ogrqhxiq8Sf2", + email: "foo@bar.at", + } as User); + + const service = new AuthService(instance(userServiceMock)); + + const token = await service.authenticateUser("foo@bar.at", "secret"); + + expect(token).to.not.be.null; + }); + + it("should fail (user does not exist)", async () => { + const userServiceMock: IUserService = mock(UserService); + + when(userServiceMock.getUserForAuthentication(anyString())).thenResolve(null); + + const service = new AuthService(instance(userServiceMock)); + + await expect(service.authenticateUser("foo@bar.at", "secret")).to.be.rejectedWith(UnauthorizedException); + }); + + it("should fail (wrong password)", async () => { + const userServiceMock: IUserService = mock(UserService); + + when(userServiceMock.getUserForAuthentication(anyString())).thenResolve({ + username: "Admin", + password: "$2b$10$l37H7il.konAYEMjLw.qWudFesOPSaKjtGj6VGw11Ogrqhxiq8Sf2", + email: "foo@bar.at", + } as User); + + const service = new AuthService(instance(userServiceMock)); + + await expect(service.authenticateUser("foo@bar.at", "somethingElse")).to.be.rejectedWith(UnauthorizedException); + }); +}); diff --git a/src/services/AuthService.ts b/src/services/AuthService.ts new file mode 100644 index 0000000..b86aa65 --- /dev/null +++ b/src/services/AuthService.ts @@ -0,0 +1,34 @@ +import IAuthService from "../interfaces/services/IAuthService"; +import InputValidator from "../common/InputValidator"; +import Encryption from "../common/Encryption"; +import JwtHelper from "../common/JwtHelper"; +import { inject, injectable } from "inversify"; +import TYPES from "../ioc/types"; +import IUserService from "../interfaces/services/IUserService"; + +import UnauthorizedException from "../exceptions/UnauthorizedException"; + +@injectable() +export default class AuthService implements IAuthService { + private userService: IUserService; + + constructor(@inject(TYPES.IUserService) userService: IUserService) { + this.userService = userService; + } + + public async authenticateUser(email: string, password: string): Promise { + const data = await this.userService.getUserForAuthentication(email); + + if (!InputValidator.isSet(data)) { + throw new UnauthorizedException("Authentication failed"); + } + + const isValidPassword = await Encryption.compare(password, data.password); + + if (!isValidPassword) { + throw new UnauthorizedException("Authentication failed"); + } + + return JwtHelper.generateToken(data.userID); + } +} diff --git a/src/services/BuildingService.spec.ts b/src/services/BuildingService.spec.ts new file mode 100644 index 0000000..ad093ea --- /dev/null +++ b/src/services/BuildingService.spec.ts @@ -0,0 +1,429 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ + +import { anyNumber, anything, instance, mock, when } from "ts-mockito"; + +import IPlanetService from "../interfaces/services/IPlanetService"; +import PlanetService from "./PlanetService"; +import IRequirementsService from "../interfaces/services/IRequirementsService"; +import RequirementsService from "./RequirementsService"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; +import ITechnologiesRepository from "../interfaces/repositories/ITechnologiesRepository"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import UserRepository from "../repositories/UserRepository"; +import TechnologiesRepository from "../repositories/TechnologiesRepository"; +import PlanetRepository from "../repositories/PlanetRepository"; +import BuildingRepository from "../repositories/BuildingRepository"; +import Planet from "../units/Planet"; +import User from "../units/User"; +import Techs from "../units/Techs"; +import Buildings from "../units/Buildings"; +import BuildingService from "./BuildingService"; +import BuildBuildingRequest from "../entities/requests/BuildBuildingRequest"; +import ApiException from "../exceptions/ApiException"; +import { Globals } from "../common/Globals"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; + +const chai = require("chai"); +const chaiAsPromised = require("chai-as-promised"); +const expect = chai.expect; +chai.use(chaiAsPromised); + +describe("BuildingService", () => { + it("should start building", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(true); + + when(planetRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + ownerID: 1, + metal: 100000, + crystal: 100000, + deuterium: 100000, + energyMax: 100000, + isUpgradingBuilding(): boolean { + return false; + }, + } as Planet); + + when(userRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + } as User); + + when(technologiesRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + } as Techs); + + when(buildingRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + metalMine: 0, + roboticFactory: 5, + naniteFactory: 5, + } as Buildings); + + when(requirementsServiceMock.fulfilled(anything(), anything(), anything())).thenReturn(true); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: 1, + } as BuildBuildingRequest; + + const userID = 1; + + const result: Planet = await service.start(request, userID); + + expect(result.bBuildingId).equals(request.buildingID); + expect(result.bBuildingEndTime).greaterThan(Math.floor(Date.now() / 1000)); + }); + + it("should fail (not enough resources)", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(true); + + when(planetRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + ownerID: 1, + metal: 10, + crystal: 10, + deuterium: 10, + energyMax: 10, + isUpgradingBuilding(): boolean { + return false; + }, + } as Planet); + + when(userRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + } as User); + + when(technologiesRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + } as Techs); + + when(buildingRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + metalMine: 0, + roboticFactory: 5, + naniteFactory: 5, + } as Buildings); + + when(requirementsServiceMock.fulfilled(anything(), anything(), anything())).thenReturn(true); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: 1, + } as BuildBuildingRequest; + + const userID = 1; + + await expect(service.start(request, userID)).to.be.rejectedWith(ApiException, "Not enough resources"); + }); + + it("should fail (requirements not met)", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(true); + + when(planetRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + ownerID: 1, + metal: 10, + crystal: 10, + deuterium: 10, + energyMax: 10, + isUpgradingBuilding(): boolean { + return false; + }, + } as Planet); + + when(userRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + } as User); + + when(technologiesRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + } as Techs); + + when(buildingRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + metalMine: 0, + roboticFactory: 5, + naniteFactory: 5, + } as Buildings); + + when(requirementsServiceMock.fulfilled(anything(), anything(), anything())).thenReturn(false); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: 1, + } as BuildBuildingRequest; + + const userID = 1; + + await expect(service.start(request, userID)).to.be.rejectedWith(ApiException, "Requirements are not met"); + }); + + it("should fail (user is researching)", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(true); + + when(planetRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + ownerID: 1, + metal: 10, + crystal: 10, + deuterium: 10, + energyMax: 10, + isUpgradingBuilding(): boolean { + return false; + }, + } as Planet); + + when(userRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + isResearching(): boolean { + return true; + }, + } as User); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: Globals.Buildings.RESEARCH_LAB, + } as BuildBuildingRequest; + + const userID = 1; + + await expect(service.start(request, userID)).to.be.rejectedWith( + ApiException, + "Can't build this building while it is in use", + ); + }); + + it("should fail (user is building units)", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(true); + + when(planetRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + ownerID: 1, + metal: 10, + crystal: 10, + deuterium: 10, + energyMax: 10, + isBuildingUnits(): boolean { + return true; + }, + isUpgradingBuilding(): boolean { + return false; + }, + } as Planet); + + when(userRepositoryMock.getById(anyNumber())).thenResolve({ + userID: 1, + } as User); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: Globals.Buildings.ROBOTIC_FACTORY, + } as BuildBuildingRequest; + + const userID = 1; + + await expect(service.start(request, userID)).to.be.rejectedWith( + ApiException, + "Can't build this building while it is in use", + ); + }); + + it("should fail (user is building a building)", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(true); + + when(planetRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + ownerID: 1, + metal: 10, + crystal: 10, + deuterium: 10, + energyMax: 10, + isUpgradingBuilding(): boolean { + return true; + }, + } as Planet); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: Globals.Buildings.ROBOTIC_FACTORY, + } as BuildBuildingRequest; + + const userID = 1; + + await expect(service.start(request, userID)).to.be.rejectedWith(ApiException, "Planet already has a build-job"); + }); + + it("should fail (user does not own the planet)", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(true); + + when(planetRepositoryMock.getById(anyNumber())).thenResolve({ + planetID: 1, + ownerID: 2, + metal: 10, + crystal: 10, + deuterium: 10, + energyMax: 10, + isUpgradingBuilding(): boolean { + return true; + }, + } as Planet); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: Globals.Buildings.ROBOTIC_FACTORY, + } as BuildBuildingRequest; + + const userID = 1; + + await expect(service.start(request, userID)).to.be.rejectedWith( + UnauthorizedException, + "User does not own the planet", + ); + }); + + it("should fail (planet does not exist)", async () => { + const planetServiceMock: IPlanetService = mock(PlanetService); + const requirementsServiceMock: IRequirementsService = mock(RequirementsService); + const buildingRepositoryMock: IBuildingRepository = mock(BuildingRepository); + const planetRepositoryMock: IPlanetRepository = mock(PlanetRepository); + const technologiesRepositoryMock: ITechnologiesRepository = mock(TechnologiesRepository); + const userRepositoryMock: IUserRepository = mock(UserRepository); + + when(buildingRepositoryMock.exists(anyNumber())).thenResolve(false); + + const service = new BuildingService( + instance(planetServiceMock), + instance(requirementsServiceMock), + instance(buildingRepositoryMock), + instance(planetRepositoryMock), + instance(technologiesRepositoryMock), + instance(userRepositoryMock), + ); + + const request = { + planetID: 1, + buildingID: Globals.Buildings.ROBOTIC_FACTORY, + } as BuildBuildingRequest; + + const userID = 1; + + await expect(service.start(request, userID)).to.be.rejectedWith( + NonExistingEntityException, + "Planet does not exist", + ); + }); +}); diff --git a/src/services/BuildingService.ts b/src/services/BuildingService.ts index 47349b6..0bebc29 100644 --- a/src/services/BuildingService.ts +++ b/src/services/BuildingService.ts @@ -1,47 +1,221 @@ -import Database from "../common/Database"; -import InputValidator from "../common/InputValidator"; -import SerializationHelper from "../common/SerializationHelper"; -import IBuildingService from "../interfaces/IBuildingService"; +import IBuildingService from "../interfaces/services/IBuildingService"; + +import { inject, injectable } from "inversify"; +import Planet from "../units/Planet"; import Buildings from "../units/Buildings"; +import User from "../units/User"; + +import { Globals } from "../common/Globals"; + +import Config from "../common/Config"; +import Calculations from "../common/Calculations"; +import TYPES from "../ioc/types"; +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import BuildBuildingRequest from "../entities/requests/BuildBuildingRequest"; +import ApiException from "../exceptions/ApiException"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; +import Techs from "../units/Techs"; +import ITechnologiesRepository from "../interfaces/repositories/ITechnologiesRepository"; +import IRequirementsService from "../interfaces/services/IRequirementsService"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import InputValidator from "../common/InputValidator"; + +import IUnitCosts from "../interfaces/IUnitCosts"; -import squel = require("safe-squel"); +import DemolishBuildingRequest from "../entities/requests/DemolishBuildingRequest"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; +import InvalidParameterException from "../exceptions/InvalidParameterException"; -/** - * This class defines a service to interact with the buildings-table in the database - */ +@injectable() export default class BuildingService implements IBuildingService { - /** - * Returns a list of buildings on a given planet - * @param planetID the ID of the planet - */ - public async getBuildings(planetID: number): Promise { - const query: string = squel - .select() - .from("buildings", "b") - .where("b.planetID = ?", planetID) - .toString(); + private planetService: IPlanetService; + private requirementsService: IRequirementsService; + + private buildingRepository: IBuildingRepository; + private planetRepository: IPlanetRepository; + private technologiesRepository: ITechnologiesRepository; + private userRepository: IUserRepository; + + constructor( + @inject(TYPES.IPlanetService) planetService: IPlanetService, + @inject(TYPES.IRequirementsService) requirementsService: IRequirementsService, + @inject(TYPES.IBuildingRepository) buildingRepository: IBuildingRepository, + @inject(TYPES.IPlanetRepository) planetRepository: IPlanetRepository, + @inject(TYPES.ITechnologiesRepository) technologiesRepository: ITechnologiesRepository, + @inject(TYPES.IUserRepository) userRepository: IUserRepository, + ) { + this.planetService = planetService; + this.buildingRepository = buildingRepository; + this.planetRepository = planetRepository; + this.technologiesRepository = technologiesRepository; + this.userRepository = userRepository; + this.requirementsService = requirementsService; + } + + public async start(request: BuildBuildingRequest, userID: number): Promise { + if (!(await this.buildingRepository.exists(request.planetID))) { + throw new NonExistingEntityException("Planet does not exist"); + } + + const planet: Planet = await this.planetRepository.getById(request.planetID); + + if (planet.ownerID !== userID) { + throw new UnauthorizedException("User does not own the planet"); + } + + if (planet.isUpgradingBuilding()) { + throw new ApiException("Planet already has a build-job"); + } - const [rows] = await Database.query(query); + if ( + (request.buildingID === Globals.Buildings.ROBOTIC_FACTORY || + request.buildingID === Globals.Buildings.NANITE_FACTORY || + request.buildingID === Globals.Buildings.SHIPYARD) && + planet.isBuildingUnits() + ) { + throw new ApiException("Can't build this building while it is in use"); + } + + const user: User = await this.userRepository.getById(userID); + + if (request.buildingID === Globals.Buildings.RESEARCH_LAB && user.isResearching()) { + throw new ApiException("Can't build this building while it is in use"); + } + + const requirements = Config.getGameConfig().units.buildings.find(r => r.unitID === request.buildingID).requirements; + const technolgies: Techs = await this.technologiesRepository.getById(userID); + const buildings: Buildings = await this.buildingRepository.getById(request.planetID); + + if (!this.requirementsService.fulfilled(requirements, buildings, technolgies)) { + throw new ApiException("Requirements are not met"); + } + + // 3. check if there are enough resources on the planet for the building to be built + const buildingKey = Globals.UnitNames[request.buildingID]; + const currentLevel = buildings[buildingKey]; - if (!InputValidator.isSet(rows)) { - return null; + const cost = Calculations.getCosts(request.buildingID, currentLevel); + + // TODO: update planet resources to the current tick before checking them + + if ( + planet.metal < cost.metal || + planet.crystal < cost.crystal || + planet.deuterium < cost.deuterium || + planet.energyUsed < cost.energy + ) { + throw new ApiException("Not enough resources"); } - return SerializationHelper.toInstance(new Buildings(), JSON.stringify(rows[0])); + // 4. start the build-job + const buildTime: number = Calculations.calculateBuildTimeInSeconds( + cost.metal, + cost.crystal, + buildings.roboticFactory, + buildings.naniteFactory, + ); + + const endTime: number = Math.round(+new Date() / 1000) + buildTime; + + planet.metal = planet.metal - cost.metal; + planet.crystal = planet.crystal - cost.crystal; + planet.deuterium = planet.deuterium - cost.deuterium; + planet.bBuildingId = request.buildingID; + planet.bBuildingEndTime = endTime; + + await this.planetRepository.save(planet); + + return planet; } - /** - * Creates a new row in the database. - * @param planetID the ID of the planet - * @param connection a connection from the connection-pool, if this query should be executed within a transaction - */ - public async createBuildingsRow(planetID: number, connection = null) { - const query = `INSERT INTO buildings (\`planetID\`) VALUES (${planetID});`; + public async getAll(planetID: number, userID: number): Promise { + if (!(await this.planetService.checkOwnership(userID, planetID))) { + throw new UnauthorizedException("Player does not own the planet"); + } - if (connection === null) { - return await Database.query(query); + return await this.buildingRepository.getById(planetID); + } + + public async cancel(planetID: number, userID: number): Promise { + if (!(await this.planetService.checkOwnership(userID, planetID))) { + throw new UnauthorizedException("Player does not own the planet"); + } + + const planet: Planet = await this.planetRepository.getById(planetID); + const buildings: Buildings = await this.buildingRepository.getById(planetID); + + if (!InputValidator.isSet(planet) || !InputValidator.isSet(buildings)) { + throw new NonExistingEntityException("Planet does not exist"); + } + + if (!planet.isUpgradingBuilding()) { + throw new ApiException("Planet has no build-job"); + } + + const buildingKey = Globals.UnitNames[planet.bBuildingId]; + + const currentLevel = buildings[buildingKey]; + + const cost: IUnitCosts = Calculations.getCosts(planet.bBuildingId, currentLevel); + + planet.bBuildingId = 0; + planet.bBuildingEndTime = 0; + planet.metal = planet.metal + cost.metal; + planet.crystal = planet.crystal + cost.crystal; + planet.deuterium = planet.deuterium + cost.deuterium; + + await this.planetRepository.save(planet); + + return planet; + } + + public async demolish(request: DemolishBuildingRequest, userID: number): Promise { + const planet: Planet = await this.planetRepository.getById(request.planetID); + + if (!InputValidator.isSet(planet)) { + throw new NonExistingEntityException("The planet does not exist"); } - return await connection.query(query); + if (planet.ownerID !== userID) { + throw new UnauthorizedException("User does not own the planet"); + } + + const buildings: Buildings = await this.buildingRepository.getById(request.planetID); + + const buildingKey = Globals.UnitNames[request.buildingID]; + const currentLevel = buildings[buildingKey]; + + if (currentLevel === 0) { + throw new ApiException("This building can't be demolished"); + } + + if (!InputValidator.isSet(planet) || !InputValidator.isSet(buildings)) { + throw new InvalidParameterException("Invalid parameter"); + } + + if (planet.isUpgradingBuilding()) { + throw new ApiException("Planet already has a build-job"); + } + + const cost = Calculations.getCosts(request.buildingID, currentLevel - 1); + + const buildTime: number = Calculations.calculateBuildTimeInSeconds( + cost.metal, + cost.crystal, + buildings.roboticFactory, + buildings.naniteFactory, + ); + + const endTime: number = Math.round(+new Date() / 1000) + buildTime; + + planet.bBuildingId = request.buildingID; + planet.bBuildingEndTime = endTime; + planet.bBuildingDemolition = true; + + await this.planetRepository.save(planet); + + return planet; } } diff --git a/src/services/DefenseService.ts b/src/services/DefenseService.ts index 9e3d838..56155d1 100644 --- a/src/services/DefenseService.ts +++ b/src/services/DefenseService.ts @@ -1,44 +1,177 @@ -import Database from "../common/Database"; -import IDefenseService from "../interfaces/IDefenseService"; -import squel = require("safe-squel"); +import IDefenseService from "../interfaces/services/IDefenseService"; +import { inject, injectable } from "inversify"; +import Defenses from "../units/Defenses"; +import TYPES from "../ioc/types"; +import IDefenseRepository from "../interfaces/repositories/IDefenseRepository"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; -/** - * This class defines a service to interact with the defenses-table in the database - */ +import ApiException from "../exceptions/ApiException"; +import Buildings from "../units/Buildings"; +import Planet from "../units/Planet"; +import Calculations from "../common/Calculations"; +import Queue from "../common/Queue"; +import IUnitCosts from "../interfaces/IUnitCosts"; +import QueueItem from "../common/QueueItem"; +import BuildDefenseRequest from "../entities/requests/BuildDefenseRequest"; +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; + +@injectable() export default class DefenseService implements IDefenseService { - /** - * Returns a list of defenses on a given planet owner by a given user - * @param userID the ID of the user - * @param planetID the ID of the planet - */ - public async getDefenses(userID: number, planetID: number) { - const query: string = squel - .select() - .field("p.ownerID", "ownerID") - .field("d.*") - .from("defenses", "d") - .left_join("planets", "p", "d.planetID = p.planetID") - .where("d.planetID = ?", planetID) - .where("p.ownerID = ?", userID) - .toString(); - - const [[rows]] = await Database.query(query); - - return rows; + @inject(TYPES.IDefenseRepository) private defenseRepository: IDefenseRepository; + @inject(TYPES.IPlanetRepository) private planetRepository: IPlanetRepository; + @inject(TYPES.IBuildingRepository) private buildingRepository: IBuildingRepository; + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + + public async getAll(userID: number, planetID: number): Promise { + if (!(await this.planetRepository.exists(planetID))) { + throw new NonExistingEntityException("Planet does not exist"); + } + + if (!(await this.planetService.checkOwnership(userID, planetID))) { + throw new UnauthorizedException("User does not own the planet"); + } + + return await this.defenseRepository.getById(planetID); } - /** - * Creates a new row in the database. - * @param planetID the ID of the planet - * @param connection a connection from the connection-pool, if this query should be executed within a transaction - */ - public async createDefenseRow(planetID: number, connection = null) { - const query = `INSERT INTO defenses (\`planetID\`) VALUES (${planetID});`; + public async processBuildOrder(request: BuildDefenseRequest, userID: number): Promise { + if (!(await this.planetRepository.exists(request.planetID))) { + throw new NonExistingEntityException("Planet does not exist"); + } + + const planet: Planet = await this.planetRepository.getById(request.planetID); + + if (planet.ownerID !== userID) { + throw new UnauthorizedException("User does not own the planet"); + } + + const buildings: Buildings = await this.buildingRepository.getById(request.planetID); + const defenses: Defenses = await this.defenseRepository.getById(request.planetID); + + if (planet.isUpgradingHangar()) { + throw new ApiException("Shipyard is currently upgrading"); + } + + let metal = planet.metal; + let crystal = planet.crystal; + let deuterium = planet.deuterium; + + let stopProcessing = false; + let buildTime = 0; + + let freeSiloSlots: number = Calculations.calculateFreeMissileSlots( + buildings.missileSilo, + defenses.antiBallisticMissile, + defenses.interplanetaryMissile, + ); + + const queue: Queue = new Queue(); + + // TODO: put this into a separate function + for (const buildOrder of request.buildOrder) { + let count = buildOrder.amount; + + const cost: IUnitCosts = Calculations.getCosts(buildOrder.unitID, 1); + + // if the user has not enough ressources to fullfill the complete build-order + if (metal < cost.metal * count || crystal < cost.crystal * count || deuterium < cost.deuterium * count) { + let tempCount: number; - if (connection === null) { - return await Database.query(query); + if (cost.metal > 0) { + tempCount = metal / cost.metal; + + if (tempCount < count) { + count = tempCount; + } + } + + if (cost.crystal > 0) { + tempCount = crystal / cost.crystal; + + if (tempCount < count) { + count = tempCount; + } + } + + if (cost.deuterium > 0) { + tempCount = deuterium / cost.deuterium; + + if (tempCount < count) { + count = tempCount; + } + } + + // no need to further process the queue + stopProcessing = true; + } + + // check free slots in silo + if (buildOrder.unitID === 309) { + // can't build any more rockets + if (freeSiloSlots === 0) { + buildOrder.amount = 0; + } else { + buildOrder.amount = Math.min(freeSiloSlots, buildOrder.amount); + freeSiloSlots -= buildOrder.amount; + } + } + + if (buildOrder.unitID === 310) { + // can't build any more rockets + if (freeSiloSlots === 0) { + buildOrder.amount = 0; + } else { + buildOrder.amount = Math.floor(freeSiloSlots / 2) * buildOrder.amount; + freeSiloSlots -= buildOrder.amount; + } + } + + // build time in seconds + buildTime += + Calculations.calculateBuildTimeInSeconds( + cost.metal, + cost.crystal, + buildings.shipyard, + buildings.naniteFactory, + ) * Math.floor(count); + + queue.getQueue().push(new QueueItem(buildOrder.unitID, Math.floor(count))); + + metal -= cost.metal * count; + crystal -= cost.crystal * count; + deuterium -= cost.deuterium * count; + + if (stopProcessing) { + break; + } + } + + queue.setTimeRemaining(buildTime); + queue.setLastUpdateTime(Math.floor(Date.now() / 1000)); + + let oldBuildOrder; + + if (!planet.isBuildingUnits()) { + planet.bHangarQueue = JSON.parse("[]"); + oldBuildOrder = planet.bHangarQueue; + planet.bHangarStartTime = Math.floor(Date.now() / 1000); + } else { + oldBuildOrder = JSON.parse(planet.bHangarQueue); } - return await connection.query(query); + oldBuildOrder.push(queue); + + planet.bHangarQueue = JSON.stringify(oldBuildOrder); + + planet.metal = metal; + planet.crystal = crystal; + planet.deuterium = deuterium; + + await this.planetRepository.save(planet); + + return planet; } } diff --git a/src/services/EventService.ts b/src/services/EventService.ts index 6232617..acfa924 100644 --- a/src/services/EventService.ts +++ b/src/services/EventService.ts @@ -1,19 +1,14 @@ import Database from "../common/Database"; import InputValidator from "../common/InputValidator"; import SerializationHelper from "../common/SerializationHelper"; -import IEventService from "../interfaces/IEventService"; +import IEventService from "../interfaces/services/IEventService"; import Event from "../units/Event"; import squel = require("safe-squel"); +import { injectable } from "inversify"; -/** - * This class defines a service to interact manage events - */ +@injectable() export default class EventService implements IEventService { - /** - * - * @param event - */ - public async createNewEvent(event: Event) { + public async create(event: Event) { const query: string = squel .insert() .into("events") @@ -34,12 +29,7 @@ export default class EventService implements IEventService { return await Database.query(query); } - /** - * Returns an event of a user - * @param userID the ID of the user - * @param eventID the ID of the event - */ - public async getEventOfPlayer(userID: number, eventID: number): Promise { + public async getEvent(userID: number, eventID: number): Promise { const query: string = squel .select() .from("events") @@ -56,11 +46,7 @@ export default class EventService implements IEventService { return SerializationHelper.toInstance(new Event(), JSON.stringify(result)); } - /** - * Cancels an event - * @param event the event to be canceled - */ - public async cancelEvent(event: Event) { + public async cancel(event: Event) { const query: string = squel .update() .table("events") diff --git a/src/services/GalaxyService.ts b/src/services/GalaxyService.ts index 85dcc6a..28fa80b 100644 --- a/src/services/GalaxyService.ts +++ b/src/services/GalaxyService.ts @@ -1,97 +1,29 @@ -import Database from "../common/Database"; -import { Globals } from "../common/Globals"; +import IGalaxyService from "../interfaces/services/IGalaxyService"; + +import { inject, injectable } from "inversify"; +import TYPES from "../ioc/types"; +import IGalaxyRepository from "../interfaces/repositories/IGalaxyRepository"; +import GalaxyPositionInfo from "../units/GalaxyPositionInfo"; +import IGameConfig from "../interfaces/IGameConfig"; +import Config from "../common/Config"; import ICoordinates from "../interfaces/ICoordinates"; -import IGalaxyService from "../interfaces/IGalaxyService"; -import PlanetType = Globals.PlanetType; -import squel = require("safe-squel"); - -/** - * This class defines a service to interact with the galaxy-table in the database - */ +@injectable() export default class GalaxyService implements IGalaxyService { - /** - * Returns all information for a given galaxy-position - * @param posGalaxy the galaxy - * @param posSystem the system - */ - public async getGalaxyInfo(posGalaxy: number, posSystem: number) { - const query: string = squel - .select() - .field("p.planetID") - .field("p.ownerID") - .field("u.username") - .field("p.name") - .field("p.posGalaxy") - .field("p.posSystem") - .field("p.posPlanet") - .field("p.lastUpdate") - .field("p.planetType") - .field("p.image") - .field("g.debrisMetal") - .field("g.debrisCrystal") - .field("p.destroyed") - .from("galaxy", "g") - .left_join("planets", "p", "g.planetID = p.planetID") - .left_join("users", "u", "u.userID = p.ownerID") - .where("p.posGalaxy = ?", posGalaxy) - .where("p.posSystem = ?", posSystem) - .toString(); - - const [rows] = await Database.query(query); + @inject(TYPES.IGalaxyRepository) private galaxyRepository: IGalaxyRepository; - return rows; + public async getPositionInfo(posGalaxy: number, posSystem: number): Promise { + return await this.galaxyRepository.getPositionInfo(posGalaxy, posSystem); } - /** - * Returns a not yet populated position in the universe within the given boundaries - * @param maxGalaxy the maximum galaxy-position - * @param maxSystem the maximum system-position - * @param minPlanet the minimum planet-position - * @param maxPlanet the maximum planet-position - */ - public async getFreePosition( - maxGalaxy: number, - maxSystem: number, - minPlanet: number, - maxPlanet: number, - ): Promise { - const queryUser = `CALL getFreePosition(${maxGalaxy}, ${maxSystem}, ${minPlanet}, ${maxPlanet});`; - - const [[[result]]] = await Database.query(queryUser); - - return { - posGalaxy: result.posGalaxy, - posSystem: result.posSystem, - posPlanet: result.posPlanet, - type: PlanetType.PLANET, - }; - } - - /** - * Creates a new row in the database. - * @param planetID the ID of the planet - * @param posGalaxy the galaxy-position - * @param posSystem the system-position - * @param posPlanet the planet-position - * @param connection a connection from the connection-pool, if this query should be executed within a transaction - */ - public async createGalaxyRow( - planetID: number, - posGalaxy: number, - posSystem: number, - posPlanet: number, - connection = null, - ) { - /* tslint:disable:max-line-length*/ - // eslint-disable-next-line max-len - const query = `INSERT INTO galaxy(\`planetID\`, \`posGalaxy\`, \`posSystem\`, \`posPlanet\`) VALUES (${planetID}, ${posGalaxy}, ${posSystem}, ${posPlanet});`; - /* tslint:enable:max-line-length*/ - - if (connection === null) { - return await Database.query(query); - } + public async getFreePosition(): Promise { + const gameConfig: IGameConfig = Config.getGameConfig(); - return await connection.query(query); + return await this.galaxyRepository.getFreePosition( + gameConfig.server.limits.galaxy.max, + gameConfig.server.limits.system.max, + gameConfig.server.startPlanet.minPlanetPos, + gameConfig.server.startPlanet.maxPlanetPos, + ); } } diff --git a/src/services/MessageService.ts b/src/services/MessageService.ts index b7c9afe..f83ac9f 100644 --- a/src/services/MessageService.ts +++ b/src/services/MessageService.ts @@ -1,123 +1,71 @@ -import Database from "../common/Database"; -import InputValidator from "../common/InputValidator"; -import SerializationHelper from "../common/SerializationHelper"; -import IMessageService from "../interfaces/IMessageService"; +import IMessageService from "../interfaces/services/IMessageService"; + +import { inject, injectable } from "inversify"; import Message from "../units/Message"; +import TYPES from "../ioc/types"; +import IMessageRepository from "../interfaces/repositories/IMessageRepository"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import InputValidator from "../common/InputValidator"; -import squel = require("safe-squel"); +import SendMessageRequest from "../entities/requests/SendMessageRequest"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; +import ApiException from "../exceptions/ApiException"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; -/** - * This class defines a service to interact with the messages-table in the database - */ +@injectable() export default class MessageService implements IMessageService { - /** - * Returns a list of all messages a user has sent or received - * @param userID the ID of the user - */ - public async getAllMessages(userID: number) { - const query: string = squel - .select() - .from("messages") - .field("messageID") - .field("senderID") - .field("receiverID") - .field("sendtime") - .field("type") - .field("subject") - .field("body") - .where("receiverID = ?", userID) - .where("deleted = ?", 0) - .order("sendtime", false) - .toString(); - - const [rows] = await Database.query(query); - - if (!InputValidator.isSet(rows)) { - return null; - } + @inject(TYPES.IMessageRepository) private messageRepository: IMessageRepository; + @inject(TYPES.IUserRepository) private userRepository: IUserRepository; - return rows; + public async getAll(userID: number): Promise { + return await this.messageRepository.getAll(userID); } - /** - * Returns a specific message given the messageID and the sender- or receiverID - * @param userID the sender- or receiverID - * @param messageID the ID of the message - */ - public async getMessageById(userID: number, messageID: number): Promise { - const query: string = squel - .select() - .from("messages") - .field("messageID") - .field("senderID") - .field("receiverID") - .field("sendtime") - .field("type") - .field("subject") - .field("body") - .where("receiverID = ?", userID) - .where("messageID = ?", messageID) - .where("deleted = ?", 0) - .toString(); - - const [rows] = await Database.query(query.toString()); - - if (!InputValidator.isSet(rows)) { - return null; + public async getById(messageID: number, userID: number): Promise { + const message = await this.messageRepository.getById(messageID); + + if (!InputValidator.isSet(message)) { + throw new NonExistingEntityException("Message does not exist"); + } + + if (message.receiverID !== userID) { + throw new UnauthorizedException("User was not the receiver"); } - return SerializationHelper.toInstance(new Message(), JSON.stringify(rows[0])); + return message; } - /** - * Marks a message as deleted in the database - * @param userID the ID of the user - * @param messageID the ID of the message - */ - public async deleteMessage(userID: number, messageID: number) { - const query: string = squel - .update() - .table("messages") - .set("deleted", 1) - .where("messageID = ?", messageID) - .where("receiverID = ?", userID) - .toString(); - - const [rows] = await Database.query(query); - - if (!InputValidator.isSet(rows)) { - return null; + public async send(request: SendMessageRequest, userID: number): Promise { + const receiver = await this.userRepository.getById(request.receiverID); + + if (!InputValidator.isSet(receiver)) { + throw new NonExistingEntityException("The receiver does not exist"); } - return rows; + const message: Message = { + senderID: userID, + receiverID: request.receiverID, + type: 1, // TODO + subject: request.subject, + body: request.body, + } as Message; + + await this.messageRepository.create(message); } - /** - * Sends a new message - * @param senderID the ID of the sender - * @param receiverID the ID of the receiver - * @param subject the subject of the message - * @param messageText the message-text - */ - public async sendMessage(senderID: number, receiverID: number, subject: string, messageText: string) { - // TODO: set unread-message-count + 1 at receiver? - const query: string = squel - .insert() - .into("messages") - .set("senderID", senderID) - .set("receiverID", receiverID) - .set( - "sendtime", - new Date() - .toISOString() - .slice(0, 19) - .replace("T", " "), - ) - .set("type", 1) - .set("subject", subject) - .set("body", messageText) - .toString(); - - await Database.query(query); + public async delete(messageID: number, userID: number): Promise { + const message: Message = await this.messageRepository.getById(messageID); + + if (!InputValidator.isSet(message)) { + throw new NonExistingEntityException("Message does not exist"); + } + + if (message.receiverID !== userID) { + throw new UnauthorizedException("Message was not sent to user"); + } + + message.deleted = true; + + await this.messageRepository.save(message); } } diff --git a/src/services/PlanetService.spec.ts b/src/services/PlanetService.spec.ts deleted file mode 100644 index 782a6c0..0000000 --- a/src/services/PlanetService.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as chai from "chai"; -import Planet from "../units/Planet"; - -const expect = chai.expect; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); - -const container = createContainer(); - -describe("PlanetService", () => { - it("should return a planet", async () => { - const ownerID = 1; - const planetID = 167546850; - - const result = await container.planetService.getPlanet(ownerID, planetID); - - expect(result.ownerID).to.be.equals(ownerID); - expect(result.planetID).to.be.equals(planetID); - }); - - it("should update a planet", async () => { - const planet: Planet = await container.planetService.getPlanet(1, 167546850, true); - - planet.name = "SomethingElse"; - - const result = await container.planetService.updatePlanet(planet); - - expect(result).to.be.equals(planet); - }); -}); diff --git a/src/services/PlanetService.ts b/src/services/PlanetService.ts index c3e4beb..554251a 100644 --- a/src/services/PlanetService.ts +++ b/src/services/PlanetService.ts @@ -1,297 +1,81 @@ -import Database from "../common/Database"; -import InputValidator from "../common/InputValidator"; -import SerializationHelper from "../common/SerializationHelper"; -import ICoordinates from "../interfaces/ICoordinates"; -import IPlanetService from "../interfaces/IPlanetService"; +import IPlanetService from "../interfaces/services/IPlanetService"; import Planet from "../units/Planet"; -import squel = require("safe-squel"); -import EntityInvalidException from "../exceptions/EntityInvalidException"; - -/** - * This class defines a service to interact with the planets-table in the database - */ -export default class PlanetService implements IPlanetService { - /** - * Returns all information about a given planet owned by the given user. - * @param userID the ID of the user - * @param planetID the ID of the planet - * @param fullInfo true - all informations are returned, false - only basic information is returned - */ - public async getPlanet(userID: number, planetID: number, fullInfo = false): Promise { - let query = squel - .select() - .from("planets", "p") - .where("p.planetID = ?", planetID) - .where("p.ownerID = ?", userID); - - if (!fullInfo) { - query = query - .field("planetID") - .field("ownerID") - .field("name") - .field("posGalaxy") - .field("posSystem") - .field("posPlanet") - .field("planetType") - .field("image"); - } - - const [rows] = await Database.query(query.toString()); - - if (!InputValidator.isSet(rows)) { - return null; - } - - return SerializationHelper.toInstance(new Planet(), JSON.stringify(rows[0])); - } - - /** - * Updates a planet given a planet-object - * @param planet the planet with changed data - */ - public async updatePlanet(planet: Planet): Promise { - let query = squel.update().table("planets"); - - if (!planet.isValid()) { - throw new EntityInvalidException("Invalid entity"); - } - - if (typeof planet.name !== "undefined") { - query = query.set("name", planet.name); - } - - if (typeof planet.lastUpdate !== "undefined") { - query = query.set("lastUpdate", planet.lastUpdate); - } - if (typeof planet.fieldsCurrent !== "undefined") { - query = query.set("fieldsCurrent", planet.fieldsCurrent); - } - - if (typeof planet.fieldsMax !== "undefined") { - query = query.set("fieldsMax", planet.fieldsMax); - } - - if (typeof planet.metal !== "undefined") { - query = query.set("metal", planet.metal); - } - - if (typeof planet.crystal !== "undefined") { - query = query.set("crystal", planet.crystal); - } - - if (typeof planet.deuterium !== "undefined") { - query = query.set("deuterium", planet.deuterium); - } - - if (typeof planet.energyUsed !== "undefined") { - query = query.set("energyUsed", planet.energyUsed); - } - - if (typeof planet.energyMax !== "undefined") { - query = query.set("energyMax", planet.energyMax); - } - - if (typeof planet.metalMinePercent !== "undefined") { - query = query.set("metalMinePercent", planet.metalMinePercent); - } - - if (typeof planet.crystalMinePercent !== "undefined") { - query = query.set("crystalMinePercent", planet.crystalMinePercent); - } +import { inject, injectable } from "inversify"; +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import Event from "../units/Event"; - if (typeof planet.deuteriumSynthesizerPercent !== "undefined") { - query = query.set("deuteriumSynthesizerPercent", planet.deuteriumSynthesizerPercent); - } - - if (typeof planet.solarPlantPercent !== "undefined") { - query = query.set("solarPlantPercent", planet.solarPlantPercent); - } - - if (typeof planet.fusionReactorPercent !== "undefined") { - query = query.set("fusionReactorPercent", planet.fusionReactorPercent); - } - - if (typeof planet.solarSatellitePercent !== "undefined") { - query = query.set("solarSatellitePercent", planet.solarSatellitePercent); - } - - if (typeof planet.bBuildingId !== "undefined") { - query = query.set("bBuildingId", planet.bBuildingId); - } - - if (typeof planet.bBuildingEndTime !== "undefined") { - query = query.set("bBuildingEndTime", planet.bBuildingEndTime); - } - - if (typeof planet.bBuildingDemolition !== "undefined") { - query = query.set("bBuildingDemolition", planet.bBuildingDemolition); - } - - if (typeof planet.bHangarQueue !== "undefined") { - query = query.set("bHangarQueue", planet.bHangarQueue); - } - - if (typeof planet.bHangarStartTime !== "undefined") { - query = query.set("bHangarStartTime", planet.bHangarStartTime); - } - - if (typeof planet.bHangarPlus !== "undefined") { - query = query.set("bHangarPlus", planet.bHangarPlus); - } - - if (typeof planet.destroyed !== "undefined") { - query = query.set("destroyed", planet.destroyed); - } +import ApiException from "../exceptions/ApiException"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import RenamePlanetRequest from "../entities/requests/RenamePlanetRequest"; +import InputValidator from "../common/InputValidator"; - query = query.where("planetID = ?", planet.planetID); +@injectable() +export default class PlanetService implements IPlanetService { + @inject(TYPES.IPlanetRepository) private planetRepository: IPlanetRepository; - await Database.query(query.toString()); + public async checkOwnership(userID: number, planetID: number): Promise { + const planet = await this.planetRepository.getById(planetID); - return planet; + return InputValidator.isSet(planet) && planet.ownerID === userID; } - /** - * Returns a new, not yet taken planetID - */ - public async getNewId(): Promise { - const query = "CALL getNewPlanetId();"; - - const [[[result]]] = await Database.query(query); - - return result.planetID; + public async getAll(userID: number): Promise { + return await this.planetRepository.getAllOfUser(userID); } - /** - * Inserts a new planet into the database given a planet-object - * @param planet the planet-object containing the information - * @param connection a connection from the connection-pool, if this query should be executed within a transaction - */ - public async createNewPlanet(planet: Planet, connection = null) { - const query = squel - .insert() - .into("planets") - .set("planetID", planet.planetID) - .set("ownerID", planet.ownerID) - .set("name", planet.name) - .set("posGalaxy", planet.posGalaxy) - .set("posSystem", planet.posSystem) - .set("posPlanet", planet.posPlanet) - .set("lastUpdate", planet.lastUpdate) - .set("planetType", planet.planetType) - .set("image", planet.image) - .set("diameter", planet.diameter) - .set("fieldsMax", planet.fieldsMax) - .set("tempMin", planet.tempMin) - .set("tempMax", planet.tempMax) - .set("metal", planet.metal) - .set("crystal", planet.crystal) - .set("deuterium", planet.deuterium) - .toString(); - - if (connection === null) { - return await Database.query(query); - } else { - return await connection.query(query); - } + public async getMovement(planetID: number, userID: number): Promise { + return await this.planetRepository.getMovement(userID, planetID); } - /** - * Returns a list of all planets of a given user - * @param userID the ID of the user - * @param fullInfo true - all informations are returned, false - only basic information is returned - */ - public async getAllPlanetsOfUser(userID: number, fullInfo = false) { - let query = squel - .select() - .from("planets") - .where("ownerID = ?", userID); + public async destroy(planetID: number, userID: number): Promise { + const planet: Planet = await this.planetRepository.getById(planetID); - if (!fullInfo) { - query = query - .field("planetID") - .field("ownerID") - .field("name") - .field("posGalaxy") - .field("posSystem") - .field("posPlanet") - .field("planetType") - .field("image"); + if (planet.ownerID !== userID) { + throw new UnauthorizedException("User does not own the planet"); } - const [rows] = await Database.query(query.toString()); + const planetList = await this.planetRepository.getAllOfUser(userID); - if (!InputValidator.isSet(rows)) { - return null; + if (planetList.length === 1) { + throw new ApiException("The last planet cannot be destroyed"); } - return rows; + return await this.planetRepository.delete(planetID, userID); + // TODO: if the deleted planet was the current planet -> set another one as current planet } - /** - * Returns a list of flights to and from a given planet owned by a given user - * @param userID the ID of the user - * @param planetID the ID of the planet - */ - public async getMovementOnPlanet(userID: number, planetID: number) { - const query: string = squel - .select() - .from("events") - .where("ownerID = ?", userID) - .where( - squel - .expr() - .or(`startID = ${planetID}`) - .or(`endID = ${planetID}`), - ) - .toString(); - - const [rows] = await Database.query(query); + public async rename(request: RenamePlanetRequest, userID: number): Promise { + const planet: Planet = await this.planetRepository.getById(request.planetID); - if (!InputValidator.isSet(rows)) { - return null; + if (planet.ownerID !== userID) { + throw new UnauthorizedException("Player does not own the planet"); } - return rows; - } + planet.name = request.newName; - /** - * Deletes a planet - * @param userID the ID of the user - * @param planetID the ID of the planet - */ - public async deletePlanet(userID: number, planetID: number) { - const query: string = squel - .delete() - .from("planets") - .where("planetID = ?", planetID) - .where("ownerID = ?", userID) - .toString(); + await this.planetRepository.save(planet); - await Database.query(query); + return planet; } - /** - * Returns a planet or moon at a given position - * @param userID - * @param position - */ - public async getPlanetOrMoonAtPosition(position: ICoordinates): Promise { - const query = squel - .select({ autoQuoteFieldNames: true }) - .from("planets") - .where("posGalaxy = ?", position.posGalaxy) - .where("posSystem = ?", position.posSystem) - .where("posPlanet = ?", position.posPlanet) - .where("planetType = ?", position.type) - .toString(); + public async getById(planetID: number, userID: number): Promise { + const planet: Planet = await this.planetRepository.getById(planetID); - const [[rows]] = await Database.query(query); - - if (!InputValidator.isSet(rows)) { - return null; + if (planet.ownerID !== userID) { + return { + planetID: planet.planetID, + ownerID: planet.ownerID, + name: planet.name, + posGalaxy: planet.posGalaxy, + posSystem: planet.posSystem, + posPlanet: planet.posPlanet, + planetType: planet.planetType, + image: planet.image, + } as Planet; } - return rows; + return planet; } } diff --git a/src/services/RequirementsService.ts b/src/services/RequirementsService.ts new file mode 100644 index 0000000..37b9496 --- /dev/null +++ b/src/services/RequirementsService.ts @@ -0,0 +1,26 @@ +import Techs from "../units/Techs"; +import Buildings from "../units/Buildings"; +import { IRequirement } from "../interfaces/IGameConfig"; +import { Globals } from "../common/Globals"; +import IRequirementsService from "../interfaces/services/IRequirementsService"; +import { injectable } from "inversify"; + +@injectable() +export default class RequirementsService implements IRequirementsService { + public fulfilled(requirements: IRequirement[], buildings: Buildings, technologies: Techs): boolean { + if (requirements !== undefined) { + requirements.forEach(function(requirement) { + const key = Globals.UnitNames[requirement.unitID]; + + if (key in buildings && buildings[key] < requirement.level) { + return false; + } + + if (key in technologies && technologies[key] < requirement.level) { + return false; + } + }); + } + return true; + } +} diff --git a/src/services/ShipService.ts b/src/services/ShipService.ts index 56c16c1..5cc3dd3 100644 --- a/src/services/ShipService.ts +++ b/src/services/ShipService.ts @@ -1,50 +1,154 @@ -import Database from "../common/Database"; +import IShipService from "../interfaces/services/IShipService"; + +import { inject, injectable } from "inversify"; +import TYPES from "../ioc/types"; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import IShipsRepository from "../interfaces/repositories/IShipsRepository"; +import ApiException from "../exceptions/ApiException"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import IPlanetService from "../interfaces/services/IPlanetService"; +import BuildShipsRequest from "../entities/requests/BuildShipsRequest"; +import Queue from "../common/Queue"; +import Planet from "../units/Planet"; +import Buildings from "../units/Buildings"; +import IUnitCosts from "../interfaces/IUnitCosts"; +import Calculations from "../common/Calculations"; +import QueueItem from "../common/QueueItem"; import InputValidator from "../common/InputValidator"; -import IShipService from "../interfaces/IShipService"; -import squel = require("safe-squel"); +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; -/** - * This class defines a service to interact with the ships-table in the database - */ +@injectable() export default class ShipService implements IShipService { - /** - * Returns a list of ships on a given planet, owned by the given user - * @param userID the ID of the user - * @param planetID the ID of the planet - */ - public async getShips(userID: number, planetID: number) { - const query: string = squel - .select() - .field("p.ownerID") - .field("s.*") - .from("ships", "s") - .left_join("planets", "p", "s.planetID = p.planetID") - .where("s.planetID = ?", planetID) - .where("p.ownerID = ?", userID) - .toString(); - - const [[rows]] = await Database.query(query.toString()); - - if (!InputValidator.isSet(rows)) { - return null; - } - - return rows; + @inject(TYPES.IShipsRepository) private shipsRepository: IShipsRepository; + @inject(TYPES.IPlanetRepository) private planetRepository: IPlanetRepository; + @inject(TYPES.IPlanetService) private planetService: IPlanetService; + @inject(TYPES.IBuildingRepository) private buildingRepository: IBuildingRepository; + + public async getAll(userID: number, planetID: number) { + if (!(await this.planetRepository.exists(planetID))) { + throw new NonExistingEntityException("Planet does not exist"); + } + + if (!(await this.planetService.checkOwnership(userID, planetID))) { + throw new UnauthorizedException("User does not own the planet"); + } + + return await this.shipsRepository.getById(planetID); } - /** - * Creates a new row in the database. - * @param planetID the ID of the planet - * @param connection a connection from the connection-pool, if this query should be executed within a transaction - */ - public async createShipsRow(planetID: number, connection = null) { - const query = `INSERT INTO ships (\`planetID\`) VALUES (${planetID});`; + public async processBuildOrder(request: BuildShipsRequest, userID: number): Promise { + const queue: Queue = new Queue(); + + const planet: Planet = await this.planetRepository.getById(request.planetID); + + if (!InputValidator.isSet(planet)) { + throw new NonExistingEntityException("Planet does not exist"); + } + + const buildings: Buildings = await this.buildingRepository.getById(request.planetID); + + if (planet.ownerID !== userID) { + throw new UnauthorizedException("The player does not own the planet"); + } + + if (planet.isUpgradingHangar()) { + throw new ApiException("Shipyard is currently upgrading"); + } + + let metal = planet.metal; + let crystal = planet.crystal; + let deuterium = planet.deuterium; + + let stopProcessing = false; + let buildTimeInSeconds = 0; + + // TODO: put into separate function (also reference this in defense-router) + for (const buildOrder of request.buildOrder) { + const cost: IUnitCosts = Calculations.getCosts(buildOrder.unitID, 1); + + // if the user has not enough ressources to fullfill the complete build-order + if ( + metal < cost.metal * buildOrder.amount || + crystal < cost.crystal * buildOrder.amount || + deuterium < cost.deuterium * buildOrder.amount + ) { + let tempCount: number; + + if (cost.metal > 0) { + tempCount = metal / cost.metal; - if (connection === null) { - return await Database.query(query); + if (tempCount < buildOrder.amount) { + buildOrder.amount = tempCount; + } + } + + if (cost.crystal > 0) { + tempCount = crystal / cost.crystal; + + if (tempCount < buildOrder.amount) { + buildOrder.amount = tempCount; + } + } + + if (cost.deuterium > 0) { + tempCount = deuterium / cost.deuterium; + + if (tempCount < buildOrder.amount) { + buildOrder.amount = tempCount; + } + } + + // no need to further process the queue + stopProcessing = true; + } + + buildTimeInSeconds += + Calculations.calculateBuildTimeInSeconds( + cost.metal, + cost.crystal, + buildings.shipyard, + buildings.naniteFactory, + ) * Math.floor(buildOrder.amount); + + queue.getQueue().push(new QueueItem(buildOrder.unitID, Math.floor(buildOrder.amount))); + + metal -= cost.metal * buildOrder.amount; + crystal -= cost.crystal * buildOrder.amount; + deuterium -= cost.deuterium * buildOrder.amount; + + if (stopProcessing) { + break; + } } - return await connection.query(query); + queue.setTimeRemaining(buildTimeInSeconds); + queue.setLastUpdateTime(Math.floor(Date.now() / 1000)); + + let oldBuildOrder; + + if (!InputValidator.isSet(planet.bHangarQueue)) { + planet.bHangarQueue = JSON.parse("[]"); + oldBuildOrder = planet.bHangarQueue; + } else { + oldBuildOrder = JSON.parse(planet.bHangarQueue); + } + + oldBuildOrder.push(queue); + + planet.bHangarQueue = JSON.stringify(oldBuildOrder); + + if (planet.bHangarStartTime === 0) { + planet.bHangarStartTime = Math.floor(Date.now() / 1000); + } + + planet.metal = metal; + planet.crystal = crystal; + planet.deuterium = deuterium; + + await this.planetRepository.save(planet); + + return planet; } } diff --git a/src/services/TechService.spec.ts b/src/services/TechService.spec.ts deleted file mode 100644 index 5ad3238..0000000 --- a/src/services/TechService.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as chai from "chai"; - -const expect = chai.expect; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const createContainer = require("../ioc/createContainer"); - -const container = createContainer(); - -describe("TechService", () => { - it("should return a planet", async () => { - try { - await container.techService.createTechRow(1); - } catch (error) { - expect(error.message).contains("Duplicate entry"); - } - }); -}); diff --git a/src/services/TechService.ts b/src/services/TechService.ts index d71e8db..846d216 100644 --- a/src/services/TechService.ts +++ b/src/services/TechService.ts @@ -1,40 +1,138 @@ -import Database from "../common/Database"; -import ITechService from "../interfaces/ITechService"; +import ITechService from "../interfaces/services/ITechService"; -import squel = require("safe-squel"); +import { inject, injectable } from "inversify"; +import TYPES from "../ioc/types"; +import ITechnologiesRepository from "../interfaces/repositories/ITechnologiesRepository"; +import Techs from "../units/Techs"; +import BuildTechRequest from "../entities/requests/BuildTechRequest"; +import Planet from "../units/Planet"; +import Buildings from "../units/Buildings"; +import User from "../units/User"; +import InputValidator from "../common/InputValidator"; +import { Globals } from "../common/Globals"; +import Config from "../common/Config"; +import Calculations from "../common/Calculations"; -/** - * This class defines a service to interact with the techs-table in the database - */ +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import IUserRepository from "../interfaces/repositories/IUserRepository"; +import ApiException from "../exceptions/ApiException"; +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import IRequirementsService from "../interfaces/services/IRequirementsService"; +import CancelTechRequest from "../entities/requests/CancelTechRequest"; +import IUnitCosts from "../interfaces/IUnitCosts"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; + +@injectable() export default class TechService implements ITechService { - /** - * Returns a list of technologies for a given user - * @param userID the ID of the user - */ - public async getTechs(userID: number) { - const query: string = squel - .select() - .from("techs") - .where("userID = ?", userID) - .toString(); - - const [[rows]] = await Database.query(query); - - return rows; + @inject(TYPES.ITechnologiesRepository) private technologiesRepository: ITechnologiesRepository; + @inject(TYPES.IPlanetRepository) private planetRepository: IPlanetRepository; + @inject(TYPES.IBuildingRepository) private buildingRepository: IBuildingRepository; + @inject(TYPES.IUserRepository) private userRepository: IUserRepository; + @inject(TYPES.IRequirementsService) private requirementsService: IRequirementsService; + + public async getAll(userID: number): Promise { + return await this.technologiesRepository.getById(userID); + } + + public async build(request: BuildTechRequest, userID: number): Promise { + const planet: Planet = await this.planetRepository.getById(request.planetID); + + if (!InputValidator.isSet(planet)) { + throw new NonExistingEntityException("Planet does not exist"); + } + + if (planet.ownerID !== userID) { + throw new UnauthorizedException("Player does not own the planet"); + } + + if (planet.isUpgradingResearchLab()) { + throw new ApiException("Planet is upgrading the research-lab"); + } + const user: User = await this.userRepository.getById(userID); + + // 1. check if there is already a build-job on the planet + if (user.isResearching()) { + throw new ApiException("Planet already has a build-job"); + } + + const buildings: Buildings = await this.buildingRepository.getById(request.planetID); + const techs: Techs = await this.technologiesRepository.getById(userID); + + // 2. check, if requirements are met + const requirements = Config.getGameConfig().units.technologies.find(r => r.unitID === request.techID).requirements; + + if (!(await this.requirementsService.fulfilled(requirements, buildings, techs))) { + throw new ApiException("Requirements are not met"); + } + + // 3. check if there are enough resources on the planet for the building to be built + const buildingKey = Globals.UnitNames[request.techID]; + + const currentLevel = techs[buildingKey]; + + const cost = Calculations.getCosts(request.techID, currentLevel); + + if ( + planet.metal < cost.metal || + planet.crystal < cost.crystal || + planet.deuterium < cost.deuterium || + planet.energyMax < cost.energy + ) { + throw new ApiException("Not enough resources"); + } + + // 4. start the build-job + const buildTime = Calculations.calculateResearchTimeInSeconds(cost.metal, cost.crystal, buildings.researchLab); + + const endTime = Math.round(+new Date() / 1000) + buildTime; + + planet.metal = planet.metal - cost.metal; + planet.crystal = planet.crystal - cost.crystal; + planet.deuterium = planet.deuterium - cost.deuterium; + user.bTechID = request.techID; + user.bTechEndTime = endTime; + + await this.planetRepository.save(planet); + await this.userRepository.save(user); + + return planet; } - /** - * Creates a new row in the database. - * @param userID the ID of the user - * @param connection a connection from the connection-pool, if this query should be executed within a transaction - */ - public async createTechRow(userID: number, connection = null) { - const query = `INSERT INTO techs (\`userID\`) VALUES (${userID});`; + public async cancel(request: CancelTechRequest, userID: number): Promise { + const planet: Planet = await this.planetRepository.getById(request.planetID); + + if (!InputValidator.isSet(planet)) { + throw new NonExistingEntityException("Planet does not exist"); + } - if (connection === null) { - return await Database.query(query); + if (planet.ownerID !== userID) { + throw new UnauthorizedException("Player does not own the planet"); } - return await connection.query(query); + const techs: Techs = await this.technologiesRepository.getById(userID); + const user: User = await this.userRepository.getById(userID); + + // 1. check if there is already a build-job on the planet + if (!user.isResearching()) { + throw new ApiException("User is not currently researching"); + } + + const techKey = Globals.UnitNames[user.bTechID]; + + const currentLevel = techs[techKey]; + + const cost: IUnitCosts = Calculations.getCosts(user.bTechID, currentLevel); + + planet.metal += cost.metal; + planet.crystal += cost.crystal; + planet.deuterium += cost.deuterium; + user.bTechID = 0; + user.bTechEndTime = 0; + + await this.planetRepository.save(planet); + await this.userRepository.save(user); + + return planet; } } diff --git a/src/services/UserService.ts b/src/services/UserService.ts index eb1b912..c15eff3 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -1,186 +1,283 @@ +import IUserService from "../interfaces/services/IUserService"; +import User from "../units/User"; +import { inject, injectable } from "inversify"; +import TYPES from "../ioc/types"; + +import IUserRepository from "../interfaces/repositories/IUserRepository"; +import CreateUserRequest from "../entities/requests/CreateUserRequest"; +import AuthSuccessResponse from "../entities/responses/AuthSuccessResponse"; +import IGameConfig from "../interfaces/IGameConfig"; +import Config from "../common/Config"; + +import Encryption from "../common/Encryption"; import Database from "../common/Database"; +import Planet from "../units/Planet"; +import DuplicateRecordException from "../exceptions/DuplicateRecordException"; +import { Globals } from "../common/Globals"; + +import JwtHelper from "../common/JwtHelper"; + +import ApiException from "../exceptions/ApiException"; +import PlanetType = Globals.PlanetType; +import IPlanetRepository from "../interfaces/repositories/IPlanetRepository"; +import IGalaxyService from "../interfaces/services/IGalaxyService"; +import IBuildingRepository from "../interfaces/repositories/IBuildingRepository"; +import IDefenseRepository from "../interfaces/repositories/IDefenseRepository"; +import IShipsRepository from "../interfaces/repositories/IShipsRepository"; +import IGalaxyRepository from "../interfaces/repositories/IGalaxyRepository"; + +import GalaxyRow from "../units/GalaxyRow"; +import ITechnologiesRepository from "../interfaces/repositories/ITechnologiesRepository"; + +import UpdateUserRequest from "../entities/requests/UpdateUserRequest"; import InputValidator from "../common/InputValidator"; -import SerializationHelper from "../common/SerializationHelper"; -import IUserService from "../interfaces/IUserService"; -import User from "../units/User"; -import squel = require("safe-squel"); +import SetCurrentPlanetRequest from "../entities/requests/SetCurrentPlanetRequest"; -/** - * This class defines a service to interact with the users-table in the database - */ +import UnauthorizedException from "../exceptions/UnauthorizedException"; +import ILogger from "../interfaces/ILogger"; +import NonExistingEntityException from "../exceptions/NonExistingEntityException"; + +@injectable() export default class UserService implements IUserService { - /** - * Returns all information about an authenticated user. - * @param userID The ID of the currently authenticated user - */ - public async getAuthenticatedUser(userID: number): Promise { - const query: string = squel - .select() - .field("*") - .from("users") - .where("userID = ?", userID) - .toString(); + @inject(TYPES.ILogger) private logger: ILogger; - const [result] = await Database.query(query); + @inject(TYPES.IUserRepository) private userRepository: IUserRepository; + @inject(TYPES.IBuildingRepository) private buildingRepository: IBuildingRepository; + @inject(TYPES.IPlanetRepository) private planetRepository: IPlanetRepository; + @inject(TYPES.IDefenseRepository) private defenseRepository: IDefenseRepository; + @inject(TYPES.IGalaxyRepository) private galaxyRepository: IGalaxyRepository; + @inject(TYPES.ITechnologiesRepository) private technologiesRepository: ITechnologiesRepository; + @inject(TYPES.IShipsRepository) private shipsRepository: IShipsRepository; + @inject(TYPES.IGalaxyService) private galaxyService: IGalaxyService; - return SerializationHelper.toInstance(new User(), JSON.stringify(result[0])); + public async getUserForAuthentication(email: string): Promise { + return await this.userRepository.getUserForAuthentication(email); } - /** - * Returns information about a user. - * This information does not contain sensible data (like email or passwords). - * @param userID The ID of the user - * @returns A user-object - */ - public async getUserById(userID: number): Promise { - const query: string = squel - .select() - .distinct() - .field("userID") - .field("username") - .from("users") - .where("userID = ?", userID) - .toString(); - - const [result] = await Database.query(query); - - if (!InputValidator.isSet(result)) { - return null; - } + public async getAuthenticatedUser(userID: number): Promise { + const user = await this.userRepository.getById(userID); - return SerializationHelper.toInstance(new User(), JSON.stringify(result[0])); + delete user.password; + + return user; } - /** - * Returns informations about a user. - * This information does contain sensible data which is needed for authentication (like email or passwords). - * @param email The email of the user - * @returns A user-object - */ - public async getUserForAuthentication(email: string): Promise { - const query: string = squel - .select({ autoQuoteFieldNames: true }) - .field("userID") - .field("email") - .field("password") - .from("users") - .where("email = ?", email) - .toString(); - - const [result] = await Database.query(query); - - if (!InputValidator.isSet(result)) { - return null; + public async getOtherUser(userID: number): Promise { + const user: User = await this.userRepository.getById(userID); + + if (!InputValidator.isSet(user)) { + throw new NonExistingEntityException("User does not exist"); } - return SerializationHelper.toInstance(new User(), JSON.stringify(result[0])); + return { + userID: user.userID, + username: user.username, + } as User; } - /** - * Checks, if a username of a email-address is already taken. - * Returns an object containing the following informations: - * ``` - * { - * username_taken: 0 (if not taken), - * email_taken: 1 (if taken) - * } - * ``` - * @param username the username - * @param email the email-address - */ - public async checkIfNameOrMailIsTaken(username: string, email: string) { - const query = - `SELECT EXISTS (SELECT 1 FROM users WHERE username LIKE '${username}') AS \`username_taken\`, ` + - `EXISTS (SELECT 1 FROM users WHERE email LIKE '${email}') AS \`email_taken\``; - - const [[data]] = await Database.query(query); - - return data; - } + public async create(request: CreateUserRequest): Promise { + const gameConfig: IGameConfig = Config.getGameConfig(); - /** - * Returns a new, not yet taken userID - * @returns The new ID - */ - public async getNewId(): Promise { - const queryUser = "CALL getNewUserId();"; + const hashedPassword = await Encryption.hash(request.password); - const [[[result]]] = await Database.query(queryUser); + const connection = await Database.getConnectionPool().getConnection(); - return result.userID; - } + const newUser: User = new User(); + const newPlanet: Planet = new Planet(); + + try { + await connection.beginTransaction(); + + if (await this.userRepository.checkEmailTaken(request.email)) { + throw new ApiException("Email is already taken"); + } + + if (await this.userRepository.checkUsernameTaken(request.username)) { + throw new ApiException("Username is already taken"); + } + + this.logger.info("Getting a new userID"); + + newUser.username = request.username; + newUser.email = request.email; + + const userID = await this.userRepository.getNewId(); + + newUser.userID = userID; + newPlanet.ownerID = userID; + newUser.password = hashedPassword; + newPlanet.planetType = PlanetType.PLANET; + + this.logger.info("Getting a new planetID"); + + const planetID = await this.planetRepository.getNewId(); + + newUser.currentPlanet = planetID; + newPlanet.planetID = planetID; + + this.logger.info("Finding free position for new planet"); + + const galaxyData = await this.galaxyService.getFreePosition(); + + newPlanet.posGalaxy = galaxyData.posGalaxy; + newPlanet.posSystem = galaxyData.posSystem; + newPlanet.posPlanet = galaxyData.posPlanet; + + this.logger.info("Creating a new user"); + + await this.userRepository.createTransactional(newUser, connection); + + this.logger.info("Creating a new planet"); + + newPlanet.name = gameConfig.server.startPlanet.name; + newPlanet.lastUpdate = Math.floor(Date.now() / 1000); + newPlanet.diameter = gameConfig.server.startPlanet.diameter; + newPlanet.fieldsMax = gameConfig.server.startPlanet.fields; + newPlanet.metal = gameConfig.server.startPlanet.resources.metal; + newPlanet.crystal = gameConfig.server.startPlanet.resources.crystal; + newPlanet.deuterium = gameConfig.server.startPlanet.resources.deuterium; + + switch (true) { + case newPlanet.posPlanet <= 5: { + newPlanet.tempMin = Math.random() * (130 - 40) + 40; + newPlanet.tempMax = Math.random() * (150 - 240) + 240; + + const images: string[] = ["desert", "dry"]; - /** - * Stores the current object in the database - * @param user A user-object - * @param connection An open database-connection, if the query should be run within a transaction - */ - public async createNewUser(user: User, connection) { - const query: string = squel - .insert({ autoQuoteFieldNames: true }) - .into("users") - .set("userID", user.userID) - .set("username", user.username) - .set("password", user.password) - .set("email", user.email) - .set("lastTimeOnline", user.lastTimeOnline) - .set("currentPlanet", user.currentPlanet) - .toString(); - - if (connection === null) { - return await Database.query(query); - } else { - return await connection.query(query); + newPlanet.image = + images[Math.floor(Math.random() * images.length)] + Math.round(Math.random() * (10 - 1) + 1) + ".png"; + + break; + } + case newPlanet.posPlanet <= 10: { + newPlanet.tempMin = Math.random() * (130 - 40) + 40; + newPlanet.tempMax = Math.random() * (150 - 240) + 240; + + const images: string[] = ["normal", "jungle", "gas"]; + + newPlanet.image = + images[Math.floor(Math.random() * images.length)] + Math.round(Math.random() * (10 - 1) + 1) + ".png"; + + break; + } + case newPlanet.posPlanet <= 15: { + newPlanet.tempMin = Math.random() * (130 - 40) + 40; + newPlanet.tempMax = Math.random() * (150 - 240) + 240; + + const images: string[] = ["ice", "water"]; + + newPlanet.image = + images[Math.floor(Math.random() * images.length)] + Math.round(Math.random() * (10 - 1) + 1) + ".png"; + } + } + + await this.planetRepository.createTransactional(newPlanet, connection); + + this.logger.info("Creating entry in buildings-table"); + + await this.buildingRepository.createTransactional(newPlanet.planetID, connection); + + this.logger.info("Creating entry in defenses-table"); + + await this.defenseRepository.createTransactional(newPlanet.planetID, connection); + + this.logger.info("Creating entry in ships-table"); + + await this.shipsRepository.createTransactional(newPlanet.planetID, connection); + + this.logger.info("Creating entry in galaxy-table"); + + const galaxyRow: GalaxyRow = { + planetID: newPlanet.planetID, + posGalaxy: newPlanet.posGalaxy, + posSystem: newPlanet.posSystem, + posPlanet: newPlanet.posPlanet, + debrisMetal: 0, + debrisCrystal: 0, + } as GalaxyRow; + + await this.galaxyRepository.createTransactional(galaxyRow, connection); + + this.logger.info("Creating entry in techs-table"); + + await this.technologiesRepository.createTransactional(newUser.userID, connection); + + connection.commit(); + + this.logger.info("Transaction complete"); + + await connection.commit(); + } catch (error) { + await connection.rollback(); + this.logger.error(error, error); + + if (error instanceof ApiException) { + throw error; + } + + if (error instanceof DuplicateRecordException || error.message.includes("Duplicate entry")) { + throw new Error(`There was an error while handling the request: ${error.message}`); + } + + throw new Error("There was an error while handling the request."); + } finally { + await connection.release(); } + + return { + token: JwtHelper.generateToken(newUser.userID), + }; } - /** - * Updates the userdata in the database - * @param user A user-object - * @param connection An open database-connection, if the query should be run within a transaction - */ - public async updateUserData(user: User, connection = null) { - let query = squel.update().table("users"); - - if (!user.isValid()) { - // TODO: throw exception - return null; - } + public async update(request: UpdateUserRequest, userID: number): Promise { + const user: User = await this.userRepository.getById(userID); - if (typeof user.username !== "undefined") { - query = query.set("username", user.username); - } + if (InputValidator.isSet(request.username)) { + if (await this.userRepository.checkUsernameTaken(request.username)) { + throw new ApiException("Username already taken"); + } - if (typeof user.password !== "undefined") { - query = query.set("password", user.password); + user.username = InputValidator.sanitizeString(request.username); } - if (typeof user.email !== "undefined") { - query = query.set("email", user.email); - } + if (InputValidator.isSet(request.password)) { + const password = InputValidator.sanitizeString(request.password); - if (typeof user.lastTimeOnline !== "undefined") { - query = query.set("lastTimeOnline", user.lastTimeOnline); + user.password = await Encryption.hash(password); } - if (typeof user.currentPlanet !== "undefined") { - query = query.set("currentPlanet", user.currentPlanet); + if (InputValidator.isSet(request.email)) { + if (await this.userRepository.checkEmailTaken(request.email)) { + throw new ApiException("Email already taken"); + } + + user.email = InputValidator.sanitizeString(request.email); } - if (typeof user.bTechID !== "undefined") { - query = query.set("bTechID", user.bTechID); + await this.userRepository.save(user); + + return user; + } + + public async setCurrentPlanet(request: SetCurrentPlanetRequest, userID: number): Promise { + const planetID = request.planetID; + + const planet: Planet = await this.planetRepository.getById(planetID); + + if (!InputValidator.isSet(planet)) { + throw new NonExistingEntityException("Planet does not exist"); } - if (typeof user.bTechEndTime !== "undefined") { - query = query.set("bTechEndTime", user.bTechEndTime); + if (planet.ownerID !== userID) { + throw new UnauthorizedException("User does not own the planet"); } - query = query.where("userID = ?", user.userID); + const user: User = await this.userRepository.getById(userID); - if (connection === null) { - return await Database.query(query.toString()); - } else { - return await connection.query(query.toString()); - } + user.currentPlanet = planetID; + + await this.userRepository.save(user); + + return user; } } diff --git a/src/tsoa/routes.ts b/src/tsoa/routes.ts new file mode 100644 index 0000000..db53ab3 --- /dev/null +++ b/src/tsoa/routes.ts @@ -0,0 +1,1577 @@ +/* tslint:disable */ +/* eslint-disable */ +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { Controller, ValidationService, FieldErrors, ValidateError, TsoaRoute, HttpStatusCodeLiteral, TsoaResponse } from 'tsoa'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { AuthRouter } from './../routes/AuthRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { BuildingsRouter } from './../routes/BuildingsRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { ConfigRouter } from './../routes/ConfigRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { DefenseRouter } from './../routes/DefenseRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { GalaxyRouter } from './../routes/GalaxyRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { MessagesRouter } from './../routes/MessagesRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { PlanetsRouter } from './../routes/PlanetsRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { ShipsRouter } from './../routes/ShipsRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { TechsRouter } from './../routes/TechsRouter'; +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +import { UserRouter } from './../routes/UserRouter'; +import { expressAuthentication } from './../middlewares/authentication'; +import { iocContainer } from './../ioc/inversify.config'; +import * as express from 'express'; + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +const models: TsoaRoute.Models = { + "AuthSuccessResponse": { + "dataType": "refObject", + "properties": { + "token": { "dataType": "string", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "AuthRequest": { + "dataType": "refObject", + "properties": { + "email": { "dataType": "string", "required": true }, + "password": { "dataType": "string", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "FailureResponse": { + "dataType": "refObject", + "properties": { + "error": { "dataType": "string", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Buildings": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "metalMine": { "dataType": "double", "required": true }, + "crystalMine": { "dataType": "double", "required": true }, + "deuteriumSynthesizer": { "dataType": "double", "required": true }, + "solarPlant": { "dataType": "double", "required": true }, + "fusionReactor": { "dataType": "double", "required": true }, + "roboticFactory": { "dataType": "double", "required": true }, + "naniteFactory": { "dataType": "double", "required": true }, + "shipyard": { "dataType": "double", "required": true }, + "metalStorage": { "dataType": "double", "required": true }, + "crystalStorage": { "dataType": "double", "required": true }, + "deuteriumStorage": { "dataType": "double", "required": true }, + "researchLab": { "dataType": "double", "required": true }, + "terraformer": { "dataType": "double", "required": true }, + "allianceDepot": { "dataType": "double", "required": true }, + "missileSilo": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Globals.PlanetType": { + "dataType": "refObject", + "properties": { + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Planet": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "ownerID": { "dataType": "double", "required": true }, + "name": { "dataType": "string", "required": true }, + "posGalaxy": { "dataType": "double", "required": true }, + "posSystem": { "dataType": "double", "required": true }, + "posPlanet": { "dataType": "double", "required": true }, + "lastUpdate": { "dataType": "double", "required": true }, + "planetType": { "ref": "Globals.PlanetType", "required": true }, + "image": { "dataType": "string", "required": true }, + "diameter": { "dataType": "double", "required": true }, + "fieldsCurrent": { "dataType": "double", "required": true }, + "fieldsMax": { "dataType": "double", "required": true }, + "tempMin": { "dataType": "double", "required": true }, + "tempMax": { "dataType": "double", "required": true }, + "metal": { "dataType": "double", "required": true }, + "crystal": { "dataType": "double", "required": true }, + "deuterium": { "dataType": "double", "required": true }, + "energyUsed": { "dataType": "double", "required": true }, + "energyMax": { "dataType": "double", "required": true }, + "metalMinePercent": { "dataType": "double", "required": true }, + "crystalMinePercent": { "dataType": "double", "required": true }, + "deuteriumSynthesizerPercent": { "dataType": "double", "required": true }, + "solarPlantPercent": { "dataType": "double", "required": true }, + "fusionReactorPercent": { "dataType": "double", "required": true }, + "solarSatellitePercent": { "dataType": "double", "required": true }, + "bBuildingId": { "dataType": "double", "required": true }, + "bBuildingEndTime": { "dataType": "double", "required": true }, + "bBuildingDemolition": { "dataType": "boolean", "required": true }, + "bHangarQueue": { "dataType": "string", "required": true }, + "bHangarStartTime": { "dataType": "double", "required": true }, + "bHangarPlus": { "dataType": "boolean", "required": true }, + "destroyed": { "dataType": "boolean", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BuildBuildingRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "buildingID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CancelBuildingRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "buildingID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DemolishBuildingRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "buildingID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IResourceCollection": { + "dataType": "refObject", + "properties": { + "metal": { "dataType": "double", "required": true }, + "crystal": { "dataType": "double", "required": true }, + "deuterium": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IStartPlanet": { + "dataType": "refObject", + "properties": { + "name": { "dataType": "string", "required": true }, + "diameter": { "dataType": "double", "required": true }, + "fields": { "dataType": "double", "required": true }, + "resources": { "ref": "IResourceCollection", "required": true }, + "minPlanetPos": { "dataType": "double", "required": true }, + "maxPlanetPos": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ILimit": { + "dataType": "refObject", + "properties": { + "min": { "dataType": "double", "required": true }, + "max": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ILimits": { + "dataType": "refObject", + "properties": { + "galaxy": { "ref": "ILimit", "required": true }, + "system": { "ref": "ILimit", "required": true }, + "position": { "ref": "ILimit", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IServer": { + "dataType": "refObject", + "properties": { + "speed": { "dataType": "double", "required": true }, + "language": { "dataType": "string", "required": true }, + "startPlanet": { "ref": "IStartPlanet", "required": true }, + "limits": { "ref": "ILimits", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ICosts": { + "dataType": "refObject", + "properties": { + "metal": { "dataType": "double", "required": true }, + "crystal": { "dataType": "double", "required": true }, + "deuterium": { "dataType": "double", "required": true }, + "energy": { "dataType": "double", "required": true }, + "factor": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IRequirement": { + "dataType": "refObject", + "properties": { + "unitID": { "dataType": "double", "required": true }, + "level": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IBuilding": { + "dataType": "refObject", + "properties": { + "unitID": { "dataType": "double", "required": true }, + "costs": { "ref": "ICosts", "required": true }, + "requirements": { "dataType": "array", "array": { "ref": "IRequirement" }, "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "ITechnology": { + "dataType": "refObject", + "properties": { + "unitID": { "dataType": "double", "required": true }, + "costs": { "ref": "ICosts", "required": true }, + "requirements": { "dataType": "array", "array": { "ref": "IRequirement" }, "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IStats": { + "dataType": "refObject", + "properties": { + "consumption": { "dataType": "double", "required": true }, + "speed": { "dataType": "double", "required": true }, + "capacity": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IRapidfire": { + "dataType": "refObject", + "properties": { + "unitID": { "dataType": "double", "required": true }, + "amount": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IShip": { + "dataType": "refObject", + "properties": { + "unitID": { "dataType": "double", "required": true }, + "costs": { "ref": "ICosts", "required": true }, + "requirements": { "dataType": "array", "array": { "ref": "IRequirement" }, "required": true }, + "stats": { "ref": "IStats", "required": true }, + "rapidfire": { "dataType": "array", "array": { "ref": "IRapidfire" }, "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IDefense": { + "dataType": "refObject", + "properties": { + "unitID": { "dataType": "double", "required": true }, + "costs": { "ref": "ICosts", "required": true }, + "requirements": { "dataType": "array", "array": { "ref": "IRequirement" }, "required": true }, + "stats": { "ref": "IStats", "required": true }, + "rapidfire": { "dataType": "array", "array": { "ref": "IRapidfire" }, "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IUnits": { + "dataType": "refObject", + "properties": { + "buildings": { "dataType": "array", "array": { "ref": "IBuilding" }, "required": true }, + "technologies": { "dataType": "array", "array": { "ref": "ITechnology" }, "required": true }, + "ships": { "dataType": "array", "array": { "ref": "IShip" }, "required": true }, + "defenses": { "dataType": "array", "array": { "ref": "IDefense" }, "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "IGameConfig": { + "dataType": "refObject", + "properties": { + "server": { "ref": "IServer", "required": true }, + "units": { "ref": "IUnits", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Defenses": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "rocketLauncher": { "dataType": "double", "required": true }, + "lightLaser": { "dataType": "double", "required": true }, + "heavyLaser": { "dataType": "double", "required": true }, + "ionCannon": { "dataType": "double", "required": true }, + "gaussCannon": { "dataType": "double", "required": true }, + "plasmaTurret": { "dataType": "double", "required": true }, + "smallShieldDome": { "dataType": "boolean", "required": true }, + "largeShieldDome": { "dataType": "boolean", "required": true }, + "antiBallisticMissile": { "dataType": "double", "required": true }, + "interplanetaryMissile": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BuildOrderItem": { + "dataType": "refObject", + "properties": { + "unitID": { "dataType": "double", "required": true }, + "amount": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BuildDefenseRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "buildOrder": { "dataType": "array", "array": { "ref": "BuildOrderItem" }, "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "GalaxyPositionInfo": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "ownerID": { "dataType": "double", "required": true }, + "username": { "dataType": "string", "required": true }, + "planetName": { "dataType": "string", "required": true }, + "posGalaxy": { "dataType": "double", "required": true }, + "posSystem": { "dataType": "double", "required": true }, + "posPlanet": { "dataType": "double", "required": true }, + "lastUpdate": { "dataType": "double", "required": true }, + "planetType": { "ref": "Globals.PlanetType", "required": true }, + "image": { "dataType": "string", "required": true }, + "debrisMetal": { "dataType": "double", "required": true }, + "debrisCrystal": { "dataType": "double", "required": true }, + "destroyed": { "dataType": "boolean", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Message": { + "dataType": "refObject", + "properties": { + "messageID": { "dataType": "double", "required": true }, + "senderID": { "dataType": "double", "required": true }, + "receiverID": { "dataType": "double", "required": true }, + "sendtime": { "dataType": "double", "required": true }, + "type": { "dataType": "double", "required": true }, + "subject": { "dataType": "string", "required": true }, + "body": { "dataType": "string", "required": true }, + "deleted": { "dataType": "boolean", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SendMessageRequest": { + "dataType": "refObject", + "properties": { + "receiverID": { "dataType": "double", "required": true }, + "subject": { "dataType": "string", "required": true }, + "body": { "dataType": "string", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DeleteMessageRequest": { + "dataType": "refObject", + "properties": { + "messageID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Event": { + "dataType": "refObject", + "properties": { + "eventID": { "dataType": "double", "required": true }, + "ownerID": { "dataType": "double", "required": true }, + "mission": { "dataType": "double", "required": true }, + "fleetlist": { "dataType": "string", "required": true }, + "startID": { "dataType": "double", "required": true }, + "startType": { "ref": "Globals.PlanetType", "required": true }, + "startTime": { "dataType": "double", "required": true }, + "endID": { "dataType": "double", "required": true }, + "endType": { "ref": "Globals.PlanetType", "required": true }, + "endTime": { "dataType": "double", "required": true }, + "loadedMetal": { "dataType": "double", "required": true }, + "loadedCrystal": { "dataType": "double", "required": true }, + "loadedDeuterium": { "dataType": "double", "required": true }, + "returning": { "dataType": "boolean", "required": true }, + "inQueue": { "dataType": "boolean", "required": true }, + "processed": { "dataType": "boolean", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "DestroyPlanetRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "RenamePlanetRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "newName": { "dataType": "string", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Ships": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "smallCargoShip": { "dataType": "double", "required": true }, + "largeCargoShip": { "dataType": "double", "required": true }, + "lightFighter": { "dataType": "double", "required": true }, + "heavyFighter": { "dataType": "double", "required": true }, + "cruiser": { "dataType": "double", "required": true }, + "battleship": { "dataType": "double", "required": true }, + "colonyShip": { "dataType": "double", "required": true }, + "recycler": { "dataType": "double", "required": true }, + "espionageProbe": { "dataType": "double", "required": true }, + "bomber": { "dataType": "double", "required": true }, + "solarSatellite": { "dataType": "double", "required": true }, + "destroyer": { "dataType": "double", "required": true }, + "battlecruiser": { "dataType": "double", "required": true }, + "deathstar": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BuildShipsRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "buildOrder": { "dataType": "array", "array": { "ref": "BuildOrderItem" }, "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "Techs": { + "dataType": "refObject", + "properties": { + "userID": { "dataType": "double", "required": true }, + "espionageTech": { "dataType": "double", "required": true }, + "computerTech": { "dataType": "double", "required": true }, + "weaponTech": { "dataType": "double", "required": true }, + "armourTech": { "dataType": "double", "required": true }, + "shieldingTech": { "dataType": "double", "required": true }, + "energyTech": { "dataType": "double", "required": true }, + "hyperspaceTech": { "dataType": "double", "required": true }, + "combustionDriveTech": { "dataType": "double", "required": true }, + "impulseDriveTech": { "dataType": "double", "required": true }, + "hyperspaceDriveTech": { "dataType": "double", "required": true }, + "laserTech": { "dataType": "double", "required": true }, + "ionTech": { "dataType": "double", "required": true }, + "plasmaTech": { "dataType": "double", "required": true }, + "intergalacticResearchTech": { "dataType": "double", "required": true }, + "gravitonTech": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "BuildTechRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + "techID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CancelTechRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "User": { + "dataType": "refObject", + "properties": { + "userID": { "dataType": "double", "required": true }, + "username": { "dataType": "string", "required": true }, + "password": { "dataType": "string", "required": true }, + "email": { "dataType": "string", "required": true }, + "lastTimeOnline": { "dataType": "double", "required": true }, + "currentPlanet": { "dataType": "double", "required": true }, + "bTechID": { "dataType": "double", "required": true }, + "bTechEndTime": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CreateUserRequest": { + "dataType": "refObject", + "properties": { + "username": { "dataType": "string", "required": true }, + "email": { "dataType": "string", "required": true }, + "password": { "dataType": "string", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "UpdateUserRequest": { + "dataType": "refObject", + "properties": { + "username": { "dataType": "string" }, + "email": { "dataType": "string" }, + "password": { "dataType": "string" }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SetCurrentPlanetRequest": { + "dataType": "refObject", + "properties": { + "planetID": { "dataType": "double", "required": true }, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +}; +const validationService = new ValidationService(models); + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + +export function RegisterRoutes(app: express.Express) { + // ########################################################################################################### + // NOTE: If you do not see routes for all of your controllers in this file, then you might not have informed tsoa of where to look + // Please look into the "controllerPathGlobs" config option described in the readme: https://github.com/lukeautry/tsoa + // ########################################################################################################### + app.post('/v1/login', + function(request: any, response: any, next: any) { + const args = { + req: { "in": "body", "name": "req", "required": true, "ref": "AuthRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "AuthSuccessResponse" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(AuthRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.login.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/buildings/:planetID', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + request: { "in": "request", "name": "request", "required": true, "dataType": "object" }, + planetID: { "in": "path", "name": "planetID", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Buildings" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(BuildingsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getAllBuildingsOnPlanet.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/buildings/build', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "BuildBuildingRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(BuildingsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.startBuilding.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/buildings/cancel', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "CancelBuildingRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(BuildingsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.cancelBuilding.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/buildings/demolish', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "DemolishBuildingRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(BuildingsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.demolishBuilding.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/config/game', + function(request: any, response: any, next: any) { + const args = { + successResponse: { "in": "res", "name": "200", "required": true, "ref": "IGameConfig" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(ConfigRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getGameConfig.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/config/units', + function(request: any, response: any, next: any) { + const args = { + successResponse: { "in": "res", "name": "200", "required": true, "ref": "IUnits" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(ConfigRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getUnitsConfig.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/defenses/:planetID', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + planetID: { "in": "path", "name": "planetID", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Defenses" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(DefenseRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getAllDefensesOnPlanet.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/defenses/build', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "BuildDefenseRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(DefenseRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.buildDefense.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/galaxy/:posGalaxy/:posSystem', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + posGalaxy: { "in": "path", "name": "posGalaxy", "required": true, "dataType": "double" }, + posSystem: { "in": "path", "name": "posSystem", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "dataType": "array", "array": { "ref": "GalaxyPositionInfo" } }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(GalaxyRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getGalaxyInformation.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/messages', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + successResponse: { "in": "res", "name": "200", "required": true, "dataType": "array", "array": { "ref": "Message" } }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(MessagesRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getAllMessages.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/messages/:messageID', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + messageID: { "in": "path", "name": "messageID", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Message" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(MessagesRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getMessageByID.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/messages/send', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "SendMessageRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "dataType": "void" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(MessagesRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.sendMessage.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/messages/delete', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "DeleteMessageRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "dataType": "void" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(MessagesRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.deleteMessage.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/planets/movement/:planetID', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + planetID: { "in": "path", "name": "planetID", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "dataType": "array", "array": { "ref": "Event" } }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(PlanetsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getMovement.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/planets/destroy', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "DestroyPlanetRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "dataType": "void" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(PlanetsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.destroyPlanet.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/planets/rename', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "RenamePlanetRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(PlanetsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.renamePlanet.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/planets/:planetID', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + planetID: { "in": "path", "name": "planetID", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(PlanetsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getPlanetByID.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/ships/:planetID', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + request: { "in": "request", "name": "request", "required": true, "dataType": "object" }, + planetID: { "in": "path", "name": "planetID", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Ships" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(ShipsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getAllShipsOnPlanet.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/ships/build', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "BuildShipsRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(ShipsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.buildShips.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/technologies', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Techs" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(TechsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getTechs.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/technologies/build', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "BuildTechRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(TechsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.buildTech.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/technologies/cancel', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "CancelTechRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "Planet" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(TechsRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.cancelTech.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/user', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + request: { "in": "request", "name": "request", "required": true, "dataType": "object" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "User" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(UserRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getUserSelf.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/user/create', + function(request: any, response: any, next: any) { + const args = { + request: { "in": "body", "name": "request", "required": true, "ref": "CreateUserRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "AuthSuccessResponse" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(UserRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.createUser.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/user/update', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + requestModel: { "in": "body", "name": "requestModel", "required": true, "ref": "UpdateUserRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "User" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(UserRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.updateUser.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/user/planetList', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + request: { "in": "request", "name": "request", "required": true, "dataType": "object" }, + successResponse: { "in": "res", "name": "200", "required": true, "dataType": "array", "array": { "ref": "Planet" } }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(UserRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getAllPlanetsOfUser.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/v1/user/currentplanet/set', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + headers: { "in": "request", "name": "headers", "required": true, "dataType": "object" }, + request: { "in": "body", "name": "request", "required": true, "ref": "SetCurrentPlanetRequest" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "User" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(UserRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.setCurrentPlanet.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.get('/v1/user/:userID', + authenticateMiddleware([{ "jwt": [] }]), + function(request: any, response: any, next: any) { + const args = { + userID: { "in": "path", "name": "userID", "required": true, "dataType": "double" }, + successResponse: { "in": "res", "name": "200", "required": true, "ref": "User" }, + badRequestResponse: { "in": "res", "name": "400", "required": true, "ref": "FailureResponse" }, + unauthorizedResponse: { "in": "res", "name": "401", "required": true, "ref": "FailureResponse" }, + serverErrorResponse: { "in": "res", "name": "500", "required": true, "ref": "FailureResponse" }, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + } catch (err) { + return next(err); + } + + const controller: any = iocContainer.get(UserRouter); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.getUserByID.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, next); + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function authenticateMiddleware(security: TsoaRoute.Security[] = []) { + return (request: any, _response: any, next: any) => { + let responded = 0; + let success = false; + + const succeed = function(user: any) { + if (!success) { + success = true; + responded++; + request['user'] = user; + next(); + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + const fail = function(error: any) { + responded++; + if (responded == security.length && !success) { + error.status = error.status || 401; + next(error) + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + for (const secMethod of security) { + if (Object.keys(secMethod).length > 1) { + let promises: Promise[] = []; + + for (const name in secMethod) { + promises.push(expressAuthentication(request, name, secMethod[name])); + } + + Promise.all(promises) + .then((users) => { succeed(users[0]); }) + .catch(fail); + } else { + for (const name in secMethod) { + expressAuthentication(request, name, secMethod[name]) + .then(succeed) + .catch(fail); + } + } + } + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function isController(object: any): object is Controller { + return 'getHeaders' in object && 'getStatus' in object && 'setStatus' in object; + } + + function promiseHandler(controllerObj: any, promise: any, response: any, next: any) { + return Promise.resolve(promise) + .then((data: any) => { + let statusCode; + let headers; + if (isController(controllerObj)) { + headers = controllerObj.getHeaders(); + statusCode = controllerObj.getStatus(); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + returnHandler(response, statusCode, data, headers) + }) + .catch((error: any) => next(error)); + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function returnHandler(response: any, statusCode?: number, data?: any, headers: any = {}) { + Object.keys(headers).forEach((name: string) => { + response.set(name, headers[name]); + }); + if (data && typeof data.pipe === 'function' && data.readable && typeof data._read === 'function') { + data.pipe(response); + } else if (data || data === false) { // === false allows boolean result + response.status(statusCode || 200).json(data); + } else { + response.status(statusCode || 204).end(); + } + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function responder(response: any): TsoaResponse { + return function(status, data, headers) { + returnHandler(response, status, data, headers); + }; + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + function getValidatedArgs(args: any, request: any, response: any): any[] { + const fieldErrors: FieldErrors = {}; + const values = Object.keys(args).map((key) => { + const name = args[key].name; + switch (args[key].in) { + case 'request': + return request; + case 'query': + return validationService.ValidateParam(args[key], request.query[name], name, fieldErrors, undefined, { "noImplicitAdditionalProperties": "throw-on-extras" }); + case 'path': + return validationService.ValidateParam(args[key], request.params[name], name, fieldErrors, undefined, { "noImplicitAdditionalProperties": "throw-on-extras" }); + case 'header': + return validationService.ValidateParam(args[key], request.header(name), name, fieldErrors, undefined, { "noImplicitAdditionalProperties": "throw-on-extras" }); + case 'body': + return validationService.ValidateParam(args[key], request.body, name, fieldErrors, undefined, { "noImplicitAdditionalProperties": "throw-on-extras" }); + case 'body-prop': + return validationService.ValidateParam(args[key], request.body[name], name, fieldErrors, 'body.', { "noImplicitAdditionalProperties": "throw-on-extras" }); + case 'res': + return responder(response); + } + }); + + if (Object.keys(fieldErrors).length > 0) { + throw new ValidateError(fieldErrors, ''); + } + return values; + } + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa +} + +// WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/src/tsoa/swagger.json b/src/tsoa/swagger.json new file mode 100644 index 0000000..1565520 --- /dev/null +++ b/src/tsoa/swagger.json @@ -0,0 +1,3290 @@ +{ + "components": { + "examples": {}, + "headers": {}, + "parameters": {}, + "requestBodies": {}, + "responses": {}, + "schemas": { + "AuthSuccessResponse": { + "properties": { + "token": { + "type": "string" + } + }, + "required": [ + "token" + ], + "type": "object", + "additionalProperties": false + }, + "AuthRequest": { + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "email", + "password" + ], + "type": "object", + "additionalProperties": false + }, + "FailureResponse": { + "properties": { + "error": { + "type": "string" + } + }, + "required": [ + "error" + ], + "type": "object", + "additionalProperties": false + }, + "Buildings": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "metalMine": { + "type": "number", + "format": "double" + }, + "crystalMine": { + "type": "number", + "format": "double" + }, + "deuteriumSynthesizer": { + "type": "number", + "format": "double" + }, + "solarPlant": { + "type": "number", + "format": "double" + }, + "fusionReactor": { + "type": "number", + "format": "double" + }, + "roboticFactory": { + "type": "number", + "format": "double" + }, + "naniteFactory": { + "type": "number", + "format": "double" + }, + "shipyard": { + "type": "number", + "format": "double" + }, + "metalStorage": { + "type": "number", + "format": "double" + }, + "crystalStorage": { + "type": "number", + "format": "double" + }, + "deuteriumStorage": { + "type": "number", + "format": "double" + }, + "researchLab": { + "type": "number", + "format": "double" + }, + "terraformer": { + "type": "number", + "format": "double" + }, + "allianceDepot": { + "type": "number", + "format": "double" + }, + "missileSilo": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID", + "metalMine", + "crystalMine", + "deuteriumSynthesizer", + "solarPlant", + "fusionReactor", + "roboticFactory", + "naniteFactory", + "shipyard", + "metalStorage", + "crystalStorage", + "deuteriumStorage", + "researchLab", + "terraformer", + "allianceDepot", + "missileSilo" + ], + "type": "object", + "additionalProperties": false + }, + "Globals.PlanetType": { + "properties": {}, + "type": "object", + "additionalProperties": false + }, + "Planet": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "ownerID": { + "type": "number", + "format": "double" + }, + "name": { + "type": "string" + }, + "posGalaxy": { + "type": "number", + "format": "double" + }, + "posSystem": { + "type": "number", + "format": "double" + }, + "posPlanet": { + "type": "number", + "format": "double" + }, + "lastUpdate": { + "type": "number", + "format": "double" + }, + "planetType": { + "$ref": "#/components/schemas/Globals.PlanetType" + }, + "image": { + "type": "string" + }, + "diameter": { + "type": "number", + "format": "double" + }, + "fieldsCurrent": { + "type": "number", + "format": "double" + }, + "fieldsMax": { + "type": "number", + "format": "double" + }, + "tempMin": { + "type": "number", + "format": "double" + }, + "tempMax": { + "type": "number", + "format": "double" + }, + "metal": { + "type": "number", + "format": "double" + }, + "crystal": { + "type": "number", + "format": "double" + }, + "deuterium": { + "type": "number", + "format": "double" + }, + "energyUsed": { + "type": "number", + "format": "double" + }, + "energyMax": { + "type": "number", + "format": "double" + }, + "metalMinePercent": { + "type": "number", + "format": "double" + }, + "crystalMinePercent": { + "type": "number", + "format": "double" + }, + "deuteriumSynthesizerPercent": { + "type": "number", + "format": "double" + }, + "solarPlantPercent": { + "type": "number", + "format": "double" + }, + "fusionReactorPercent": { + "type": "number", + "format": "double" + }, + "solarSatellitePercent": { + "type": "number", + "format": "double" + }, + "bBuildingId": { + "type": "number", + "format": "double" + }, + "bBuildingEndTime": { + "type": "number", + "format": "double" + }, + "bBuildingDemolition": { + "type": "boolean" + }, + "bHangarQueue": { + "type": "string" + }, + "bHangarStartTime": { + "type": "number", + "format": "double" + }, + "bHangarPlus": { + "type": "boolean" + }, + "destroyed": { + "type": "boolean" + } + }, + "required": [ + "planetID", + "ownerID", + "name", + "posGalaxy", + "posSystem", + "posPlanet", + "lastUpdate", + "planetType", + "image", + "diameter", + "fieldsCurrent", + "fieldsMax", + "tempMin", + "tempMax", + "metal", + "crystal", + "deuterium", + "energyUsed", + "energyMax", + "metalMinePercent", + "crystalMinePercent", + "deuteriumSynthesizerPercent", + "solarPlantPercent", + "fusionReactorPercent", + "solarSatellitePercent", + "bBuildingId", + "bBuildingEndTime", + "bBuildingDemolition", + "bHangarQueue", + "bHangarStartTime", + "bHangarPlus", + "destroyed" + ], + "type": "object", + "additionalProperties": false + }, + "BuildBuildingRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "buildingID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID", + "buildingID" + ], + "type": "object", + "additionalProperties": false + }, + "CancelBuildingRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "buildingID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID", + "buildingID" + ], + "type": "object", + "additionalProperties": false + }, + "DemolishBuildingRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "buildingID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID", + "buildingID" + ], + "type": "object", + "additionalProperties": false + }, + "IResourceCollection": { + "properties": { + "metal": { + "type": "number", + "format": "double" + }, + "crystal": { + "type": "number", + "format": "double" + }, + "deuterium": { + "type": "number", + "format": "double" + } + }, + "required": [ + "metal", + "crystal", + "deuterium" + ], + "type": "object", + "additionalProperties": false + }, + "IStartPlanet": { + "properties": { + "name": { + "type": "string" + }, + "diameter": { + "type": "number", + "format": "double" + }, + "fields": { + "type": "number", + "format": "double" + }, + "resources": { + "$ref": "#/components/schemas/IResourceCollection" + }, + "minPlanetPos": { + "type": "number", + "format": "double" + }, + "maxPlanetPos": { + "type": "number", + "format": "double" + } + }, + "required": [ + "name", + "diameter", + "fields", + "resources", + "minPlanetPos", + "maxPlanetPos" + ], + "type": "object", + "additionalProperties": false + }, + "ILimit": { + "properties": { + "min": { + "type": "number", + "format": "double" + }, + "max": { + "type": "number", + "format": "double" + } + }, + "required": [ + "min", + "max" + ], + "type": "object", + "additionalProperties": false + }, + "ILimits": { + "properties": { + "galaxy": { + "$ref": "#/components/schemas/ILimit" + }, + "system": { + "$ref": "#/components/schemas/ILimit" + }, + "position": { + "$ref": "#/components/schemas/ILimit" + } + }, + "required": [ + "galaxy", + "system", + "position" + ], + "type": "object", + "additionalProperties": false + }, + "IServer": { + "properties": { + "speed": { + "type": "number", + "format": "double" + }, + "language": { + "type": "string" + }, + "startPlanet": { + "$ref": "#/components/schemas/IStartPlanet" + }, + "limits": { + "$ref": "#/components/schemas/ILimits" + } + }, + "required": [ + "speed", + "language", + "startPlanet", + "limits" + ], + "type": "object", + "additionalProperties": false + }, + "ICosts": { + "properties": { + "metal": { + "type": "number", + "format": "double" + }, + "crystal": { + "type": "number", + "format": "double" + }, + "deuterium": { + "type": "number", + "format": "double" + }, + "energy": { + "type": "number", + "format": "double" + }, + "factor": { + "type": "number", + "format": "double" + } + }, + "required": [ + "metal", + "crystal", + "deuterium", + "energy", + "factor" + ], + "type": "object", + "additionalProperties": false + }, + "IRequirement": { + "properties": { + "unitID": { + "type": "number", + "format": "double" + }, + "level": { + "type": "number", + "format": "double" + } + }, + "required": [ + "unitID", + "level" + ], + "type": "object", + "additionalProperties": false + }, + "IBuilding": { + "properties": { + "unitID": { + "type": "number", + "format": "double" + }, + "costs": { + "$ref": "#/components/schemas/ICosts" + }, + "requirements": { + "items": { + "$ref": "#/components/schemas/IRequirement" + }, + "type": "array" + } + }, + "required": [ + "unitID", + "costs", + "requirements" + ], + "type": "object", + "additionalProperties": false + }, + "ITechnology": { + "properties": { + "unitID": { + "type": "number", + "format": "double" + }, + "costs": { + "$ref": "#/components/schemas/ICosts" + }, + "requirements": { + "items": { + "$ref": "#/components/schemas/IRequirement" + }, + "type": "array" + } + }, + "required": [ + "unitID", + "costs", + "requirements" + ], + "type": "object", + "additionalProperties": false + }, + "IStats": { + "properties": { + "consumption": { + "type": "number", + "format": "double" + }, + "speed": { + "type": "number", + "format": "double" + }, + "capacity": { + "type": "number", + "format": "double" + } + }, + "required": [ + "consumption", + "speed", + "capacity" + ], + "type": "object", + "additionalProperties": false + }, + "IRapidfire": { + "properties": { + "unitID": { + "type": "number", + "format": "double" + }, + "amount": { + "type": "number", + "format": "double" + } + }, + "required": [ + "unitID", + "amount" + ], + "type": "object", + "additionalProperties": false + }, + "IShip": { + "properties": { + "unitID": { + "type": "number", + "format": "double" + }, + "costs": { + "$ref": "#/components/schemas/ICosts" + }, + "requirements": { + "items": { + "$ref": "#/components/schemas/IRequirement" + }, + "type": "array" + }, + "stats": { + "$ref": "#/components/schemas/IStats" + }, + "rapidfire": { + "items": { + "$ref": "#/components/schemas/IRapidfire" + }, + "type": "array" + } + }, + "required": [ + "unitID", + "costs", + "requirements", + "stats", + "rapidfire" + ], + "type": "object", + "additionalProperties": false + }, + "IDefense": { + "properties": { + "unitID": { + "type": "number", + "format": "double" + }, + "costs": { + "$ref": "#/components/schemas/ICosts" + }, + "requirements": { + "items": { + "$ref": "#/components/schemas/IRequirement" + }, + "type": "array" + }, + "stats": { + "$ref": "#/components/schemas/IStats" + }, + "rapidfire": { + "items": { + "$ref": "#/components/schemas/IRapidfire" + }, + "type": "array" + } + }, + "required": [ + "unitID", + "costs", + "requirements", + "stats", + "rapidfire" + ], + "type": "object", + "additionalProperties": false + }, + "IUnits": { + "properties": { + "buildings": { + "items": { + "$ref": "#/components/schemas/IBuilding" + }, + "type": "array" + }, + "technologies": { + "items": { + "$ref": "#/components/schemas/ITechnology" + }, + "type": "array" + }, + "ships": { + "items": { + "$ref": "#/components/schemas/IShip" + }, + "type": "array" + }, + "defenses": { + "items": { + "$ref": "#/components/schemas/IDefense" + }, + "type": "array" + } + }, + "required": [ + "buildings", + "technologies", + "ships", + "defenses" + ], + "type": "object", + "additionalProperties": false + }, + "IGameConfig": { + "properties": { + "server": { + "$ref": "#/components/schemas/IServer" + }, + "units": { + "$ref": "#/components/schemas/IUnits" + } + }, + "required": [ + "server", + "units" + ], + "type": "object", + "additionalProperties": false + }, + "Defenses": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "rocketLauncher": { + "type": "number", + "format": "double" + }, + "lightLaser": { + "type": "number", + "format": "double" + }, + "heavyLaser": { + "type": "number", + "format": "double" + }, + "ionCannon": { + "type": "number", + "format": "double" + }, + "gaussCannon": { + "type": "number", + "format": "double" + }, + "plasmaTurret": { + "type": "number", + "format": "double" + }, + "smallShieldDome": { + "type": "boolean" + }, + "largeShieldDome": { + "type": "boolean" + }, + "antiBallisticMissile": { + "type": "number", + "format": "double" + }, + "interplanetaryMissile": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID", + "rocketLauncher", + "lightLaser", + "heavyLaser", + "ionCannon", + "gaussCannon", + "plasmaTurret", + "smallShieldDome", + "largeShieldDome", + "antiBallisticMissile", + "interplanetaryMissile" + ], + "type": "object", + "additionalProperties": false + }, + "BuildOrderItem": { + "properties": { + "unitID": { + "type": "number", + "format": "double" + }, + "amount": { + "type": "number", + "format": "double" + } + }, + "required": [ + "unitID", + "amount" + ], + "type": "object", + "additionalProperties": false + }, + "BuildDefenseRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "buildOrder": { + "items": { + "$ref": "#/components/schemas/BuildOrderItem" + }, + "type": "array" + } + }, + "required": [ + "planetID", + "buildOrder" + ], + "type": "object", + "additionalProperties": false + }, + "GalaxyPositionInfo": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "ownerID": { + "type": "number", + "format": "double" + }, + "username": { + "type": "string" + }, + "planetName": { + "type": "string" + }, + "posGalaxy": { + "type": "number", + "format": "double" + }, + "posSystem": { + "type": "number", + "format": "double" + }, + "posPlanet": { + "type": "number", + "format": "double" + }, + "lastUpdate": { + "type": "number", + "format": "double" + }, + "planetType": { + "$ref": "#/components/schemas/Globals.PlanetType" + }, + "image": { + "type": "string" + }, + "debrisMetal": { + "type": "number", + "format": "double" + }, + "debrisCrystal": { + "type": "number", + "format": "double" + }, + "destroyed": { + "type": "boolean" + } + }, + "required": [ + "planetID", + "ownerID", + "username", + "planetName", + "posGalaxy", + "posSystem", + "posPlanet", + "lastUpdate", + "planetType", + "image", + "debrisMetal", + "debrisCrystal", + "destroyed" + ], + "type": "object", + "additionalProperties": false + }, + "Message": { + "properties": { + "messageID": { + "type": "number", + "format": "double" + }, + "senderID": { + "type": "number", + "format": "double" + }, + "receiverID": { + "type": "number", + "format": "double" + }, + "sendtime": { + "type": "number", + "format": "double" + }, + "type": { + "type": "number", + "format": "double" + }, + "subject": { + "type": "string" + }, + "body": { + "type": "string" + }, + "deleted": { + "type": "boolean" + } + }, + "required": [ + "messageID", + "senderID", + "receiverID", + "sendtime", + "type", + "subject", + "body", + "deleted" + ], + "type": "object", + "additionalProperties": false + }, + "SendMessageRequest": { + "properties": { + "receiverID": { + "type": "number", + "format": "double" + }, + "subject": { + "type": "string" + }, + "body": { + "type": "string" + } + }, + "required": [ + "receiverID", + "subject", + "body" + ], + "type": "object", + "additionalProperties": false + }, + "DeleteMessageRequest": { + "properties": { + "messageID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "messageID" + ], + "type": "object", + "additionalProperties": false + }, + "Event": { + "properties": { + "eventID": { + "type": "number", + "format": "double" + }, + "ownerID": { + "type": "number", + "format": "double" + }, + "mission": { + "type": "number", + "format": "double" + }, + "fleetlist": { + "type": "string" + }, + "startID": { + "type": "number", + "format": "double" + }, + "startType": { + "$ref": "#/components/schemas/Globals.PlanetType" + }, + "startTime": { + "type": "number", + "format": "double" + }, + "endID": { + "type": "number", + "format": "double" + }, + "endType": { + "$ref": "#/components/schemas/Globals.PlanetType" + }, + "endTime": { + "type": "number", + "format": "double" + }, + "loadedMetal": { + "type": "number", + "format": "double" + }, + "loadedCrystal": { + "type": "number", + "format": "double" + }, + "loadedDeuterium": { + "type": "number", + "format": "double" + }, + "returning": { + "type": "boolean" + }, + "inQueue": { + "type": "boolean" + }, + "processed": { + "type": "boolean" + } + }, + "required": [ + "eventID", + "ownerID", + "mission", + "fleetlist", + "startID", + "startType", + "startTime", + "endID", + "endType", + "endTime", + "loadedMetal", + "loadedCrystal", + "loadedDeuterium", + "returning", + "inQueue", + "processed" + ], + "type": "object", + "additionalProperties": false + }, + "DestroyPlanetRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID" + ], + "type": "object", + "additionalProperties": false + }, + "RenamePlanetRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "newName": { + "type": "string" + } + }, + "required": [ + "planetID", + "newName" + ], + "type": "object", + "additionalProperties": false + }, + "Ships": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "smallCargoShip": { + "type": "number", + "format": "double" + }, + "largeCargoShip": { + "type": "number", + "format": "double" + }, + "lightFighter": { + "type": "number", + "format": "double" + }, + "heavyFighter": { + "type": "number", + "format": "double" + }, + "cruiser": { + "type": "number", + "format": "double" + }, + "battleship": { + "type": "number", + "format": "double" + }, + "colonyShip": { + "type": "number", + "format": "double" + }, + "recycler": { + "type": "number", + "format": "double" + }, + "espionageProbe": { + "type": "number", + "format": "double" + }, + "bomber": { + "type": "number", + "format": "double" + }, + "solarSatellite": { + "type": "number", + "format": "double" + }, + "destroyer": { + "type": "number", + "format": "double" + }, + "battlecruiser": { + "type": "number", + "format": "double" + }, + "deathstar": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID", + "smallCargoShip", + "largeCargoShip", + "lightFighter", + "heavyFighter", + "cruiser", + "battleship", + "colonyShip", + "recycler", + "espionageProbe", + "bomber", + "solarSatellite", + "destroyer", + "battlecruiser", + "deathstar" + ], + "type": "object", + "additionalProperties": false + }, + "BuildShipsRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "buildOrder": { + "items": { + "$ref": "#/components/schemas/BuildOrderItem" + }, + "type": "array" + } + }, + "required": [ + "planetID", + "buildOrder" + ], + "type": "object", + "additionalProperties": false + }, + "Techs": { + "properties": { + "userID": { + "type": "number", + "format": "double" + }, + "espionageTech": { + "type": "number", + "format": "double" + }, + "computerTech": { + "type": "number", + "format": "double" + }, + "weaponTech": { + "type": "number", + "format": "double" + }, + "armourTech": { + "type": "number", + "format": "double" + }, + "shieldingTech": { + "type": "number", + "format": "double" + }, + "energyTech": { + "type": "number", + "format": "double" + }, + "hyperspaceTech": { + "type": "number", + "format": "double" + }, + "combustionDriveTech": { + "type": "number", + "format": "double" + }, + "impulseDriveTech": { + "type": "number", + "format": "double" + }, + "hyperspaceDriveTech": { + "type": "number", + "format": "double" + }, + "laserTech": { + "type": "number", + "format": "double" + }, + "ionTech": { + "type": "number", + "format": "double" + }, + "plasmaTech": { + "type": "number", + "format": "double" + }, + "intergalacticResearchTech": { + "type": "number", + "format": "double" + }, + "gravitonTech": { + "type": "number", + "format": "double" + } + }, + "required": [ + "userID", + "espionageTech", + "computerTech", + "weaponTech", + "armourTech", + "shieldingTech", + "energyTech", + "hyperspaceTech", + "combustionDriveTech", + "impulseDriveTech", + "hyperspaceDriveTech", + "laserTech", + "ionTech", + "plasmaTech", + "intergalacticResearchTech", + "gravitonTech" + ], + "type": "object", + "additionalProperties": false + }, + "BuildTechRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + }, + "techID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID", + "techID" + ], + "type": "object", + "additionalProperties": false + }, + "CancelTechRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID" + ], + "type": "object", + "additionalProperties": false + }, + "User": { + "properties": { + "userID": { + "type": "number", + "format": "double" + }, + "username": { + "type": "string" + }, + "password": { + "type": "string" + }, + "email": { + "type": "string" + }, + "lastTimeOnline": { + "type": "number", + "format": "double" + }, + "currentPlanet": { + "type": "number", + "format": "double" + }, + "bTechID": { + "type": "number", + "format": "double" + }, + "bTechEndTime": { + "type": "number", + "format": "double" + } + }, + "required": [ + "userID", + "username", + "password", + "email", + "lastTimeOnline", + "currentPlanet", + "bTechID", + "bTechEndTime" + ], + "type": "object", + "additionalProperties": false + }, + "CreateUserRequest": { + "properties": { + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "username", + "email", + "password" + ], + "type": "object", + "additionalProperties": false + }, + "UpdateUserRequest": { + "properties": { + "username": { + "type": "string" + }, + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + }, + "SetCurrentPlanetRequest": { + "properties": { + "planetID": { + "type": "number", + "format": "double" + } + }, + "required": [ + "planetID" + ], + "type": "object", + "additionalProperties": false + } + }, + "securitySchemes": { + "jwt": { + "type": "apiKey", + "name": "access-token", + "in": "header" + } + } + }, + "info": { + "title": "ugamela api", + "version": "1.0.0", + "description": "Definition for the official ugamela-API which can be used to interact with an instance of an ugamela-server.", + "license": { + "name": "AGPL-3.0" + }, + "contact": { + "email": "api@ugamela.org", + "name": "the Administrator" + } + }, + "openapi": "3.0.0", + "paths": { + "/login": { + "post": { + "operationId": "Login", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthSuccessResponse" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Authentication" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthRequest" + } + } + } + } + } + }, + "/buildings/{planetID}": { + "get": { + "operationId": "GetAllBuildingsOnPlanet", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Buildings" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Buildings" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "planetID", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/buildings/build": { + "post": { + "operationId": "StartBuilding", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Buildings" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuildBuildingRequest" + } + } + } + } + } + }, + "/buildings/cancel": { + "post": { + "operationId": "CancelBuilding", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Buildings" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CancelBuildingRequest" + } + } + } + } + } + }, + "/buildings/demolish": { + "post": { + "operationId": "DemolishBuilding", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Buildings" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DemolishBuildingRequest" + } + } + } + } + } + }, + "/config/game": { + "get": { + "operationId": "GetGameConfig", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IGameConfig" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Configuration" + ], + "security": [], + "parameters": [] + } + }, + "/config/units": { + "get": { + "operationId": "GetUnitsConfig", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IUnits" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Configuration" + ], + "security": [], + "parameters": [] + } + }, + "/defenses/{planetID}": { + "get": { + "operationId": "GetAllDefensesOnPlanet", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Defenses" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Defenses" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "planetID", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/defenses/build": { + "post": { + "operationId": "BuildDefense", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Defenses" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuildDefenseRequest" + } + } + } + } + } + }, + "/galaxy/{posGalaxy}/{posSystem}": { + "get": { + "operationId": "GetGalaxyInformation", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/GalaxyPositionInfo" + }, + "type": "array" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Galaxy" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "posGalaxy", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + }, + { + "in": "path", + "name": "posSystem", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/messages": { + "get": { + "operationId": "GetAllMessages", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Message" + }, + "type": "array" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [] + } + }, + "/messages/{messageID}": { + "get": { + "operationId": "GetMessageByID", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "messageID", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/messages/send": { + "post": { + "operationId": "SendMessage", + "responses": { + "200": { + "description": "" + }, + "204": { + "description": "No content" + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendMessageRequest" + } + } + } + } + } + }, + "/messages/delete": { + "post": { + "operationId": "DeleteMessage", + "responses": { + "200": { + "description": "" + }, + "204": { + "description": "No content" + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Messages" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeleteMessageRequest" + } + } + } + } + } + }, + "/planets/movement/{planetID}": { + "get": { + "operationId": "GetMovement", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Event" + }, + "type": "array" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Planets" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "planetID", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/planets/destroy": { + "post": { + "operationId": "DestroyPlanet", + "responses": { + "200": { + "description": "" + }, + "204": { + "description": "No content" + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Planets" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DestroyPlanetRequest" + } + } + } + } + } + }, + "/planets/rename": { + "post": { + "operationId": "RenamePlanet", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Planets" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RenamePlanetRequest" + } + } + } + } + } + }, + "/planets/{planetID}": { + "get": { + "operationId": "GetPlanetByID", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Planets" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "planetID", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/ships/{planetID}": { + "get": { + "operationId": "GetAllShipsOnPlanet", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Ships" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Ships" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "planetID", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + }, + "/ships/build": { + "post": { + "operationId": "BuildShips", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Ships" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuildShipsRequest" + } + } + } + } + } + }, + "/technologies": { + "get": { + "operationId": "GetTechs", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Techs" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Technologies" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [] + } + }, + "/technologies/build": { + "post": { + "operationId": "BuildTech", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Technologies" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BuildTechRequest" + } + } + } + } + } + }, + "/technologies/cancel": { + "post": { + "operationId": "CancelTech", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Planet" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "Technologies" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CancelTechRequest" + } + } + } + } + } + }, + "/user": { + "get": { + "operationId": "GetUserSelf", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "UserData" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [] + } + }, + "/user/create": { + "post": { + "operationId": "CreateUser", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthSuccessResponse" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "UserData" + ], + "security": [], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserRequest" + } + } + } + } + } + }, + "/user/update": { + "post": { + "operationId": "UpdateUser", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "UserData" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserRequest" + } + } + } + } + } + }, + "/user/planetList": { + "get": { + "operationId": "GetAllPlanetsOfUser", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "items": { + "$ref": "#/components/schemas/Planet" + }, + "type": "array" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "UserData" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [] + } + }, + "/user/currentplanet/set": { + "post": { + "operationId": "SetCurrentPlanet", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "UserData" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SetCurrentPlanetRequest" + } + } + } + } + } + }, + "/user/{userID}": { + "get": { + "operationId": "GetUserByID", + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "401": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + }, + "500": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FailureResponse" + } + } + } + } + }, + "tags": [ + "UserData" + ], + "security": [ + { + "jwt": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "userID", + "required": true, + "schema": { + "format": "double", + "type": "number" + } + } + ] + } + } + }, + "servers": [ + { + "url": "/v1" + } + ] +} \ No newline at end of file diff --git a/src/units/AuthenticatedUser.ts b/src/units/AuthenticatedUser.ts new file mode 100644 index 0000000..cc88574 --- /dev/null +++ b/src/units/AuthenticatedUser.ts @@ -0,0 +1,26 @@ +import IUnit from "../interfaces/IUnit"; +import InputValidator from "../common/InputValidator"; + +export default class AuthenticatedUser implements IUnit { + public userID: number; + + public password: string; + + public email: string; + + public isValid(): boolean { + if (!InputValidator.isSet(this.userID) || this.userID <= 0) { + return false; + } + + if (!InputValidator.isSet(this.password) || this.password.length > 60) { + return false; + } + + if (!InputValidator.isSet(this.email) || this.email.length > 64) { + return false; + } + + return true; + } +} diff --git a/src/units/Buildings.ts b/src/units/Buildings.ts index 5d09f95..8d08dc7 100644 --- a/src/units/Buildings.ts +++ b/src/units/Buildings.ts @@ -1,92 +1,38 @@ import IUnit from "../interfaces/IUnit"; -/** - * Represents a buildings-row in the database - */ export default class Buildings implements IUnit { - /** - * The ID of the planet - */ public planetID: number; - /** - * Current metalMine level - */ public metalMine: number; - /** - * Current crystalMine level - */ public crystalMine: number; - /** - * Current deuteriumSynthesizer level - */ public deuteriumSynthesizer: number; - /** - * Current solarPlant level - */ public solarPlant: number; - /** - * Current fusionReactor level - */ public fusionReactor: number; - /** - * Current roboticFactory level - */ public roboticFactory: number; - /** - * Current naniteFactory level - */ public naniteFactory: number; - /** - * Current shipyard level - */ public shipyard: number; - /** - * Current metalStorage level - */ public metalStorage: number; - /** - * Current crystalStorage level - */ public crystalStorage: number; - /** - * Current deuteriumStorage level - */ public deuteriumStorage: number; - /** - * Current researchLab level - */ public researchLab: number; - /** - * Current terraformer level - */ public terraformer: number; - /** - * Current allianceDepot level - */ public allianceDepot: number; - /** - * Current missileSilo level - */ public missileSilo: number; - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { return ( 0 <= this.planetID && @@ -107,72 +53,4 @@ export default class Buildings implements IUnit { 0 <= this.missileSilo ); } - - // public save(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .update() - // .table("buildings") - // .set("metalMine", this.metalMine) - // .set("crystalMine", this.crystalMine) - // .set("deuteriumSynthesizer", this.deuteriumSynthesizer) - // .set("solarPlant", this.solarPlant) - // .set("fusionReactor", this.fusionReactor) - // .set("roboticFactory", this.roboticFactory) - // .set("naniteFactory", this.naniteFactory) - // .set("shipyard", this.shipyard) - // .set("metalStorage", this.metalStorage) - // .set("crystalStorage", this.crystalStorage) - // .set("deuteriumStorage", this.deuteriumStorage) - // .set("researchLab", this.researchLab) - // .set("terraformer", this.terraformer) - // .set("allianceDepot", this.allianceDepot) - // .set("missileSilo", this.missileSilo) - // .where("planetID = ?", this.planetID) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } - // - // public create(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .insert() - // .into("buildings") - // .set("planetID", this.planetID) - // .set("metalMine", this.metalMine) - // .set("crystalMine", this.crystalMine) - // .set("deuteriumSynthesizer", this.deuteriumSynthesizer) - // .set("solarPlant", this.solarPlant) - // .set("fusionReactor", this.fusionReactor) - // .set("roboticFactory", this.roboticFactory) - // .set("naniteFactory", this.naniteFactory) - // .set("shipyard", this.shipyard) - // .set("metalStorage", this.metalStorage) - // .set("crystalStorage", this.crystalStorage) - // .set("deuteriumStorage", this.deuteriumStorage) - // .set("researchLab", this.researchLab) - // .set("terraformer", this.terraformer) - // .set("allianceDepot", this.allianceDepot) - // .set("missileSilo", this.missileSilo) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } } diff --git a/src/units/Defenses.ts b/src/units/Defenses.ts index 4eda08f..f74ce64 100644 --- a/src/units/Defenses.ts +++ b/src/units/Defenses.ts @@ -1,66 +1,28 @@ import IUnit from "../interfaces/IUnit"; -/** - * Represents a defenses-row in the database - */ + export default class Defenses implements IUnit { - /** - * The ID of the planet - */ public planetID: number; - /** - * The current amount of rocketLauncher on the planet - */ public rocketLauncher: number; - /** - * The current amount of lightLaser on the planet - */ public lightLaser: number; - /** - * The current amount of heavyLaser on the planet - */ public heavyLaser: number; - /** - * The current amount of ionCannon on the planet - */ public ionCannon: number; - /** - * The current amount of gaussCannon on the planet - */ public gaussCannon: number; - /** - * The current amount of plasmaTurret on the planet - */ public plasmaTurret: number; - /** - * Is true, if the planet has a smallShieldDome - */ public smallShieldDome: boolean; - /** - * Is true, if the planet has a largeShieldDome - */ public largeShieldDome: boolean; - /** - * The current amount of antiBallisticMissile on the planet - */ public antiBallisticMissile: number; - /** - * The current amount of interplanetaryMissile on the planet - */ public interplanetaryMissile: number; - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { return ( 0 <= this.planetID && @@ -74,62 +36,4 @@ export default class Defenses implements IUnit { 0 <= this.interplanetaryMissile ); } - - // public save(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .update() - // .table("defenses") - // .set("rocketLauncher", this.rocketLauncher) - // .set("lightLaser", this.lightLaser) - // .set("heavyLaser", this.heavyLaser) - // .set("ionCannon", this.ionCannon) - // .set("gaussCannon", this.gaussCannon) - // .set("plasmaTurret", this.plasmaTurret) - // .set("smallShieldDome", this.smallShieldDome) - // .set("largeShieldDome", this.largeShieldDome) - // .set("antiBallisticMissile", this.antiBallisticMissile) - // .set("interplanetaryMissile", this.interplanetaryMissile) - // .where("planetID = ?", this.planetID) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } - // - // public create(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .insert() - // .into("defenses") - // .set("planetID", this.planetID) - // .set("rocketLauncher", this.rocketLauncher) - // .set("lightLaser", this.lightLaser) - // .set("heavyLaser", this.heavyLaser) - // .set("ionCannon", this.ionCannon) - // .set("gaussCannon", this.gaussCannon) - // .set("plasmaTurret", this.plasmaTurret) - // .set("smallShieldDome", this.smallShieldDome) - // .set("largeShieldDome", this.largeShieldDome) - // .set("antiBallisticMissile", this.antiBallisticMissile) - // .set("interplanetaryMissile", this.interplanetaryMissile) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } } diff --git a/src/units/Event.ts b/src/units/Event.ts index 35fc672..61cb5fc 100644 --- a/src/units/Event.ts +++ b/src/units/Event.ts @@ -3,19 +3,16 @@ import InputValidator from "../common/InputValidator"; import IUnit from "../interfaces/IUnit"; import PlanetType = Globals.PlanetType; -/** - * Represents a user-row in the database - */ export default class Event implements IUnit { public eventID: number; public ownerID: number; public mission: number; public fleetlist: string; public startID: number; - public startType: PlanetType; + public startType: Globals.PlanetType; public startTime: number; public endID: number; - public endType: PlanetType; + public endType: Globals.PlanetType; public endTime: number; public loadedMetal: number; public loadedCrystal: number; @@ -24,9 +21,6 @@ export default class Event implements IUnit { public inQueue: boolean; public processed: boolean; - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { if (!InputValidator.isSet(this.eventID) || this.eventID <= 0) { return false; diff --git a/src/units/GalaxyPositionInfo.ts b/src/units/GalaxyPositionInfo.ts new file mode 100644 index 0000000..71b5e1d --- /dev/null +++ b/src/units/GalaxyPositionInfo.ts @@ -0,0 +1,17 @@ +import { Globals } from "../common/Globals"; + +export default class GalaxyPositionInfo { + planetID: number; + ownerID: number; + username: string; + planetName: string; + posGalaxy: number; + posSystem: number; + posPlanet: number; + lastUpdate: number; + planetType: Globals.PlanetType; + image: string; + debrisMetal: number; + debrisCrystal: number; + destroyed: boolean; +} diff --git a/src/units/GalaxyRow.ts b/src/units/GalaxyRow.ts new file mode 100644 index 0000000..5a28d5b --- /dev/null +++ b/src/units/GalaxyRow.ts @@ -0,0 +1,8 @@ +export default class GalaxyRow { + planetID: number; + posGalaxy: number; + posSystem: number; + posPlanet: number; + debrisMetal: number; + debrisCrystal: number; +} diff --git a/src/units/Message.ts b/src/units/Message.ts index fd11f63..42c5393 100644 --- a/src/units/Message.ts +++ b/src/units/Message.ts @@ -1,52 +1,23 @@ import IUnit from "../interfaces/IUnit"; -/** - * Represents a messages-row in the database - */ + export default class Message implements IUnit { - /** - * The ID of the message - */ public messageID: number; - /** - * The ID of the sender - */ public senderID: number; - /** - * The ID of the receiver - */ public receiverID: number; - /** - * The time, the message was sent - */ public sendtime: number; - /** - * The type of the message - */ public type: number; // TODO: introduce an enum of message-types - /** - * The subject of the message - */ public subject: string; - /** - * The text of the message - */ public body: string; - /** - * If true, the message is marked as deleted - */ public deleted: boolean; - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { return ( 0 < this.messageID && diff --git a/src/units/Planet.ts b/src/units/Planet.ts index 245a761..78e44bf 100644 --- a/src/units/Planet.ts +++ b/src/units/Planet.ts @@ -1,196 +1,86 @@ import Config from "../common/Config"; import { Globals } from "../common/Globals"; import IUnit from "../interfaces/IUnit"; -/** - * Represents a planet-row in the database - */ + export default class Planet implements IUnit { - /** - * The ID of the planet - */ public planetID: number; - /** - * The ID of the owner - */ public ownerID: number; - /** - * The name of the planet - */ public name: string; - /** - * The galaxy-position in the universe - */ public posGalaxy: number; - /** - * The system-position in the universe - */ public posSystem: number; - /** - * the planet-position in the universe - */ public posPlanet: number; - /** - * The unix-timestamp the planet was last updated - */ public lastUpdate: number; - /** - * The type of the planet - */ public planetType: Globals.PlanetType; - /** - * The image of the planet - */ public image: string; - /** - * The diameter of the planet - */ public diameter: number; - /** - * The currently populated fields on the planet - */ public fieldsCurrent: number; - /** - * The maximum fields on the planet - */ public fieldsMax: number; - /** - * The minimum temperature on the planet - */ public tempMin: number; - /** - * The maximum temperature on the planet - */ public tempMax: number; - /** - * The current amount of metal on the planet - */ public metal: number; - /** - * The current amount of crystal on the planet - */ public crystal: number; - /** - * The current amount of deuterium on the planet - */ public deuterium: number; - /** - * The current amount of energy used on the planet - */ public energyUsed: number; - /** - * The maximum amount of energy on the planet - */ public energyMax: number; - /** - * The percentage of production metal-mine - */ public metalMinePercent: number; - /** - * The percentage of production of the crystal-mine - */ public crystalMinePercent: number; - /** - * The percentage of production deuterium-synthesizer - */ public deuteriumSynthesizerPercent: number; - /** - * The percentage of production solar-planet - */ public solarPlantPercent: number; - /** - * The percentage of production fusion-reactor - */ public fusionReactorPercent: number; - /** - * The percentage of production solar-sattelite - */ public solarSatellitePercent: number; - /** - * The ID of the building currently upgrading. - * This value is 0 if no building is currently upgrading. - */ public bBuildingId: number; - /** - * The time, at which the upgrade will be completed - */ public bBuildingEndTime: number; - /** - * True, if the current build-order is a demolition job - */ public bBuildingDemolition: boolean; - /** - * The curreny queue of the hangar - */ public bHangarQueue: string; - /** - * The time, the queue was started - */ public bHangarStartTime: number; // TODO: obsolete? - /** - * True, if the hangar is currently upgraded - */ + public bHangarPlus: boolean; - /** - * Indicates, if the planet is destroyed - */ public destroyed: boolean; - /** - * Checks, if the planet is currently upgrading a building - */ public isUpgradingBuilding(): boolean { return this.bBuildingId > 0 && this.bBuildingEndTime > 0; } - /** - * Checks, if the planet is currently upgrading its hangar - */ public isUpgradingHangar(): boolean { return this.bHangarPlus; } - /** - * Checks, if the planet is currently upgrading the research-lab - */ public isUpgradingResearchLab(): boolean { return this.bBuildingId === Globals.Buildings.RESEARCH_LAB && this.bBuildingEndTime > 0; } - /** - * Checks, if the planet is currently building units - */ public isBuildingUnits(): boolean { return ( this.bHangarQueue !== undefined && @@ -200,9 +90,6 @@ export default class Planet implements IUnit { ); } - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { return ( 0 < this.planetID && diff --git a/src/units/Ships.ts b/src/units/Ships.ts index 759cdc3..dc1d451 100644 --- a/src/units/Ships.ts +++ b/src/units/Ships.ts @@ -1,86 +1,36 @@ import IUnit from "../interfaces/IUnit"; -/** - * Represents a ships-row in the database - */ + export default class Ships implements IUnit { - /** - * The ID of the planet - */ public planetID: number; - /** - * The current amount of smallCargoShip - */ public smallCargoShip: number; - /** - * The current amount of largeCargoShip - */ public largeCargoShip: number; - /** - * The current amount of lightFighter - */ public lightFighter: number; - /** - * The current amount of heavyFighter - */ public heavyFighter: number; - /** - * The current amount of cruiser - */ public cruiser: number; - /** - * The current amount of battleship - */ public battleship: number; - /** - * The current amount of colonyShip - */ public colonyShip: number; - /** - * The current amount of recycler - */ public recycler: number; - /** - * The current amount of espionageProbe - */ public espionageProbe: number; - /** - * The current amount of bomber - */ public bomber: number; - /** - * The current amount of solarSatellite - */ public solarSatellite: number; - /** - * The current amount of destroyer - */ public destroyer: number; - /** - * The current amount of battlecruiser - */ public battlecruiser: number; - /** - * The current amount of deathstar - */ public deathstar: number; - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { return ( 0 < this.planetID && @@ -100,70 +50,4 @@ export default class Ships implements IUnit { 0 <= this.deathstar ); } - - // public save(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .update() - // .table("ships") - // .set("smallCargoShip", this.smallCargoShip) - // .set("largeCargoShip", this.largeCargoShip) - // .set("lightFighter", this.lightFighter) - // .set("heavyFighter", this.heavyFighter) - // .set("cruiser", this.cruiser) - // .set("battleship", this.battleship) - // .set("colonyShip", this.colonyShip) - // .set("recycler", this.recycler) - // .set("espionageProbe", this.espionageProbe) - // .set("bomber", this.bomber) - // .set("solarSatellite", this.solarSatellite) - // .set("destroyer", this.destroyer) - // .set("battlecruiser", this.battlecruiser) - // .set("deathstar", this.deathstar) - // .where("planetID = ?", this.planetID) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } - // - // public create(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .insert() - // .into("ships") - // .set("planetID", this.planetID) - // .set("smallCargoShip", this.smallCargoShip) - // .set("largeCargoShip", this.largeCargoShip) - // .set("lightFighter", this.lightFighter) - // .set("heavyFighter", this.heavyFighter) - // .set("cruiser", this.cruiser) - // .set("battleship", this.battleship) - // .set("colonyShip", this.colonyShip) - // .set("recycler", this.recycler) - // .set("espionageProbe", this.espionageProbe) - // .set("bomber", this.bomber) - // .set("solarSatellite", this.solarSatellite) - // .set("destroyer", this.destroyer) - // .set("battlecruiser", this.battlecruiser) - // .set("deathstar", this.deathstar) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } } diff --git a/src/units/Techs.ts b/src/units/Techs.ts index 5f024bd..7cb9b5c 100644 --- a/src/units/Techs.ts +++ b/src/units/Techs.ts @@ -1,91 +1,38 @@ import IUnit from "../interfaces/IUnit"; -/** - * Represents a techs-row in the database - */ + export default class Techs implements IUnit { - /** - * The ID of the user - */ public userID: number; - /** - * The current espionageTech level - */ public espionageTech: number; - /** - * The current computerTech level - */ public computerTech: number; - /** - * The current weaponTech level - */ public weaponTech: number; - /** - * The current armourTech level - */ public armourTech: number; - /** - * The current shieldingTech level - */ public shieldingTech: number; - /** - * The current energyTech level - */ public energyTech: number; - /** - * The current hyperspaceTech level - */ public hyperspaceTech: number; - /** - * The current combustionDriveTech level - */ public combustionDriveTech: number; - /** - * The current impulseDriveTech level - */ public impulseDriveTech: number; - /** - * The current hyperspaceDriveTech level - */ public hyperspaceDriveTech: number; - /** - * The current laserTech level - */ public laserTech: number; - /** - * The current ionTech level - */ public ionTech: number; - /** - * The current plasmaTech level - */ public plasmaTech: number; - /** - * The current intergalacticResearchTech level - */ public intergalacticResearchTech: number; - /** - * The current gravitonTech level - */ public gravitonTech: number; - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { return ( 0 < this.userID && @@ -106,72 +53,4 @@ export default class Techs implements IUnit { 0 <= this.gravitonTech ); } - - // public save(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .update() - // .table("techs") - // .set("espionageTech", this.espionageTech) - // .set("computerTech", this.computerTech) - // .set("weaponTech", this.weaponTech) - // .set("armourTech", this.armourTech) - // .set("shieldingTech", this.shieldingTech) - // .set("energyTech", this.energyTech) - // .set("hyperspaceTech", this.hyperspaceTech) - // .set("combustionDriveTech", this.combustionDriveTech) - // .set("impulseDriveTech", this.impulseDriveTech) - // .set("hyperspaceDriveTech", this.hyperspaceDriveTech) - // .set("laserTech", this.laserTech) - // .set("ionTech", this.ionTech) - // .set("plasmaTech", this.plasmaTech) - // .set("intergalacticResearchTech", this.intergalacticResearchTech) - // .set("gravitonTech", this.gravitonTech) - // .where("userID = ?", this.userID) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } - // - // public create(): Promise<{}> { - // return new Promise((resolve, reject) => { - // const query = squel - // .insert() - // .into("techs") - // .set("userID", this.userID) - // .set("espionageTech", this.espionageTech) - // .set("computerTech", this.computerTech) - // .set("weaponTech", this.weaponTech) - // .set("armourTech", this.armourTech) - // .set("shieldingTech", this.shieldingTech) - // .set("energyTech", this.energyTech) - // .set("hyperspaceTech", this.hyperspaceTech) - // .set("combustionDriveTech", this.combustionDriveTech) - // .set("impulseDriveTech", this.impulseDriveTech) - // .set("hyperspaceDriveTech", this.hyperspaceDriveTech) - // .set("laserTech", this.laserTech) - // .set("ionTech", this.ionTech) - // .set("plasmaTech", this.plasmaTech) - // .set("intergalacticResearchTech", this.intergalacticResearchTech) - // .set("gravitonTech", this.gravitonTech) - // .toString(); - // - // Database.query(query) - // .then(() => { - // return resolve(this); - // }) - // .catch(error => { - // Logger.error(error); - // return reject(error); - // }); - // }); - // } } diff --git a/src/units/User.ts b/src/units/User.ts index f09853d..7ec3741 100644 --- a/src/units/User.ts +++ b/src/units/User.ts @@ -2,61 +2,27 @@ import { Globals } from "../common/Globals"; import IUnit from "../interfaces/IUnit"; import InputValidator from "../common/InputValidator"; -/** - * Represents a user-row in the database - */ export default class User implements IUnit { - /** - * The ID of the user - */ public userID: number; - /** - * The name of the user - */ public username: string; - /** - * The encrypted password of the user - */ public password: string; - /** - * The e-mail address of the user - */ public email: string; - /** - * The unix-timestamp of the last time the user was online - */ public lastTimeOnline: number; - /** - * The current planet of the user - */ public currentPlanet: number; - /** - * The ID of the technology which is currently being researched. - * This value is 0 if no technology is currently being researched. - */ public bTechID: number; - /** - * The time, at which the research will be completed - */ public bTechEndTime: number; - /** - * Checks, if the planet is currently researching - */ public isResearching(): boolean { return this.bTechID > 0 && this.bTechEndTime > 0; } - /** - * Returns, if the contains valid data or not - */ public isValid(): boolean { if (!InputValidator.isSet(this.userID) || this.userID <= 0) { return false; diff --git a/tsconfig.json b/tsconfig.json index 268108d..387c9f8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,17 +1,21 @@ { "compilerOptions": { - "watch": true, "target": "es6", "module": "commonjs", "outDir": "dist", + "lib": ["es6", "dom"], "types": [ "node", - "mocha" + "mocha", + "reflect-metadata" ], "typeRoots" : [ "node_modules/@types" ], - "resolveJsonModule": true + "resolveJsonModule": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "moduleResolution": "node" }, "compileOnSave": true, "include": [ diff --git a/tsoa.json b/tsoa.json new file mode 100644 index 0000000..50140a5 --- /dev/null +++ b/tsoa.json @@ -0,0 +1,40 @@ +{ + "entryFile":"./src/App.ts", + "noImplicitAdditionalProperties":"throw-on-extras", + "controllerPathGlobs":[ + "./src/routes/*Router.ts" + ], + "ignore": [ + "**/node_modules/**" + ], + "routes":{ + "basePath":"/v1", + "routesDir":"./src/tsoa/", + "iocModule":"./src/ioc/inversify.config.ts", + "authenticationModule": "./src/middlewares/authentication.ts" + }, + "spec":{ + "basePath":"/v1", + "contact":{ + "email":"api@ugamela.org", + "name":"the Administrator" + }, + "securityDefinitions": { + "jwt": { + "type": "apiKey", + "name": "access-token", + "in": "header" + } + }, + "description":"Definition for the official ugamela-API which can be used to interact with an instance of an ugamela-server.", + "license":"AGPL-3.0", + "name":"ugamela api", + "outputDirectory":"./src/tsoa/", + "schemes":[ + "http", + "https" + ], + "specVersion":3, + "version":"1.0.0" + } +}