Merge branch 'hotfix/fix-bearer-token'
All checks were successful
continuous-integration/drone/tag Build is passing
All checks were successful
continuous-integration/drone/tag Build is passing
This commit is contained in:
21
.drone.yml
21
.drone.yml
@@ -1,6 +1,6 @@
|
|||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: default
|
name: frontend
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Build and push frontend image
|
- name: Build and push frontend image
|
||||||
@@ -14,7 +14,18 @@ steps:
|
|||||||
context: frontend/
|
context: frontend/
|
||||||
repo: docker.humenius.me/humenius/ts-onlinetime-ranks-frontend
|
repo: docker.humenius.me/humenius/ts-onlinetime-ranks-frontend
|
||||||
registry: docker.humenius.me
|
registry: docker.humenius.me
|
||||||
tags: ["latest", "v1.${DRONE_BUILD_NUMBER}"]
|
tags: ["latest", "${DRONE_SEMVER}"]
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
ref:
|
||||||
|
- refs/tags/* # only trigger when tagging
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: backend
|
||||||
|
|
||||||
|
steps:
|
||||||
- name: Build and push backend image
|
- name: Build and push backend image
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
@@ -26,4 +37,8 @@ steps:
|
|||||||
context: backend/
|
context: backend/
|
||||||
repo: docker.humenius.me/humenius/ts-onlinetime-ranks-backend
|
repo: docker.humenius.me/humenius/ts-onlinetime-ranks-backend
|
||||||
registry: docker.humenius.me
|
registry: docker.humenius.me
|
||||||
tags: ["latest", "v1.${DRONE_BUILD_NUMBER}"]
|
tags: ["latest", "${DRONE_SEMVER}"]
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
ref:
|
||||||
|
- refs/tags/* # only trigger when tagging
|
||||||
@@ -10,6 +10,6 @@ FROM node:14.3.0-alpine
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=builder /app .
|
COPY --from=builder /app .
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3500
|
||||||
CMD ["npm", "run", "start:prod"]
|
CMD ["npm", "run", "start:prod"]
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ COPY package-lock.json .
|
|||||||
RUN npm install
|
RUN npm install
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3500
|
||||||
CMD ["npm", "run", "start:${ENVIRONMENT}"]
|
CMD ["npm", "run", "start:${ENVIRONMENT}"]
|
||||||
|
|
||||||
|
|||||||
74
backend/package-lock.json
generated
74
backend/package-lock.json
generated
@@ -2470,6 +2470,16 @@
|
|||||||
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
"integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"bindings": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"file-uri-to-path": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"bluebird": {
|
"bluebird": {
|
||||||
"version": "3.7.2",
|
"version": "3.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||||
@@ -4565,6 +4575,13 @@
|
|||||||
"flat-cache": "^2.0.1"
|
"flat-cache": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"file-uri-to-path": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"fill-range": {
|
"fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
@@ -7360,9 +7377,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.19",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||||
},
|
},
|
||||||
"lodash.memoize": {
|
"lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
@@ -7856,6 +7873,13 @@
|
|||||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"nan": {
|
||||||
|
"version": "2.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||||
|
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
|
||||||
|
"dev": true,
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||||
@@ -10379,9 +10403,9 @@
|
|||||||
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
|
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
|
||||||
},
|
},
|
||||||
"ts-jest": {
|
"ts-jest": {
|
||||||
"version": "25.2.1",
|
"version": "26.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.1.tgz",
|
||||||
"integrity": "sha512-TnntkEEjuXq/Gxpw7xToarmHbAafgCaAzOpnajnFC6jI7oo1trMzAHA04eWpc3MhV6+yvhE8uUBAmN+teRJh0A==",
|
"integrity": "sha512-Lk/357quLg5jJFyBQLnSbhycnB3FPe+e9i7ahxokyXxAYoB0q1pPmqxxRPYr4smJic1Rjcf7MXDBhZWgxlli0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bs-logger": "0.x",
|
"bs-logger": "0.x",
|
||||||
@@ -10390,10 +10414,10 @@
|
|||||||
"json5": "2.x",
|
"json5": "2.x",
|
||||||
"lodash.memoize": "4.x",
|
"lodash.memoize": "4.x",
|
||||||
"make-error": "1.x",
|
"make-error": "1.x",
|
||||||
"mkdirp": "0.x",
|
"micromatch": "4.x",
|
||||||
"resolve": "1.x",
|
"mkdirp": "1.x",
|
||||||
"semver": "^5.5",
|
"semver": "7.x",
|
||||||
"yargs-parser": "^16.1.0"
|
"yargs-parser": "18.x"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"json5": {
|
"json5": {
|
||||||
@@ -10405,15 +10429,27 @@
|
|||||||
"minimist": "^1.2.5"
|
"minimist": "^1.2.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"yargs-parser": {
|
"micromatch": {
|
||||||
"version": "16.1.0",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
||||||
"integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==",
|
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"camelcase": "^5.0.0",
|
"braces": "^3.0.1",
|
||||||
"decamelize": "^1.2.0"
|
"picomatch": "^2.0.5"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"mkdirp": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "7.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
|
||||||
|
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
|
||||||
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -10937,7 +10973,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
|
||||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"requires": {
|
||||||
|
"bindings": "^1.5.0",
|
||||||
|
"nan": "^2.12.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "backend",
|
"name": "ts-onlinetime-ranks-backend",
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"description": "",
|
"description": "Backend microservice for TeamSpeak 3 Online Time Ranking",
|
||||||
"author": "",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -46,7 +45,7 @@
|
|||||||
"jest": "^25.1.0",
|
"jest": "^25.1.0",
|
||||||
"prettier": "^1.19.1",
|
"prettier": "^1.19.1",
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"ts-jest": "25.2.1",
|
"ts-jest": "^26.1.1",
|
||||||
"ts-loader": "^6.2.1",
|
"ts-loader": "^6.2.1",
|
||||||
"ts-node": "^8.6.2",
|
"ts-node": "^8.6.2",
|
||||||
"tsconfig-paths": "^3.9.0",
|
"tsconfig-paths": "^3.9.0",
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ import { AppModule } from './app.module';
|
|||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.enableCors();
|
app.enableCors();
|
||||||
await app.listen(3000);
|
await app.listen(3500);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
12
backend/src/models/RequestError.ts
Normal file
12
backend/src/models/RequestError.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
interface RequestError extends Error {
|
||||||
|
response?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestErrorConstructor extends ErrorConstructor {
|
||||||
|
new(message?: string): RequestError;
|
||||||
|
(message?: string): RequestError;
|
||||||
|
readonly prototype: RequestError;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var RequestError: RequestErrorConstructor;
|
||||||
@@ -25,19 +25,23 @@ export class SinusBotService {
|
|||||||
private bearer: string;
|
private bearer: string;
|
||||||
|
|
||||||
public async fetchStats(): Promise<TableEntry[]> {
|
public async fetchStats(): Promise<TableEntry[]> {
|
||||||
if (this.bearer == null) {
|
// Skip check as either way
|
||||||
logger.info(`Hey! I'm trying to get my Bearer token!`);
|
// - An interval needs to reset this.bearer to null
|
||||||
await this.login()
|
// - The Sinusbot token is not a JWT => Expiration date is not decodable
|
||||||
.then(token => this.bearer = token)
|
// - Estimated expiration time: 1d?
|
||||||
.catch(error => {
|
// - I don't know if it makes a difference to check via interval or to just fetch the token
|
||||||
logger.error(`Oh oh! Something went wrong while fetching Bearer token.`, error);
|
// everytime a request is sent against this API.
|
||||||
throw this.createHttpException(HttpStatus.UNAUTHORIZED, error.message);
|
// if (this.bearer == null) {
|
||||||
});
|
logger.info(`Hey! I'm trying to get my Bearer token!`);
|
||||||
logger.debug(
|
await this.login()
|
||||||
`Seems like I have my Bearer token!
|
.then(token => this.bearer = token)
|
||||||
Looks like it's ${this.bearer == null ? 'undefined' : 'not undefined'}`
|
.then(() => {
|
||||||
);
|
logger.debug(
|
||||||
}
|
`Seems like I have my Bearer token!
|
||||||
|
Looks like it's ${this.bearer == null ? 'undefined' : 'not undefined'}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// }
|
||||||
|
|
||||||
logger.info(`I try to fetch user data now! The URL is called ${this.tunaKillURL}`);
|
logger.info(`I try to fetch user data now! The URL is called ${this.tunaKillURL}`);
|
||||||
return await fetch(this.tunaKillURL, this.requestConfig(null, this.bearer))
|
return await fetch(this.tunaKillURL, this.requestConfig(null, this.bearer))
|
||||||
@@ -46,7 +50,7 @@ export class SinusBotService {
|
|||||||
.then(data => this.consumeTunakillResponse(data))
|
.then(data => this.consumeTunakillResponse(data))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.error(`I couldn't fetch user data.`, error);
|
logger.error(`I couldn't fetch user data.`, error);
|
||||||
throw this.createHttpException(error.statusCode, error.message);
|
throw this.createHttpException(HttpStatus.INTERNAL_SERVER_ERROR, error.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +82,15 @@ export class SinusBotService {
|
|||||||
return await fetch(this.loginURL, this.requestConfig(JSON.stringify(body)))
|
return await fetch(this.loginURL, this.requestConfig(JSON.stringify(body)))
|
||||||
.then(res => this.checkStatus(res))
|
.then(res => this.checkStatus(res))
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => data.token);
|
.then(data => data.token)
|
||||||
|
.catch(error => {
|
||||||
|
logger.error(`Oh oh! Something went wrong while fetching Bearer token.`, error);
|
||||||
|
throw this.createHttpException(
|
||||||
|
HttpStatus.UNAUTHORIZED,
|
||||||
|
`Fetching Bearer token for Sinusbot failed.
|
||||||
|
Please refresh page or try again later!`
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchDefaultBotId(): Promise<string> {
|
private async fetchDefaultBotId(): Promise<string> {
|
||||||
@@ -90,12 +102,12 @@ export class SinusBotService {
|
|||||||
|
|
||||||
private consumeTunakillResponse(data: any): TableEntry[] {
|
private consumeTunakillResponse(data: any): TableEntry[] {
|
||||||
if (!(data !== null || data[0] !== null || data[0].data !== null)) {
|
if (!(data !== null || data[0] !== null || data[0].data !== null)) {
|
||||||
throw Error('Response from SinusBot does not have any data to parse.')
|
throw Error('Response from SinusBot does not have any data to parse.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = data[0].data;
|
const response = data[0].data;
|
||||||
if (!(response.length > 0)) {
|
if (!(response.length > 0)) {
|
||||||
throw Error('User list is empty.')
|
throw Error('Response from SinusBot does not have any data to parse.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -119,7 +131,7 @@ export class SinusBotService {
|
|||||||
headers: {
|
headers: {
|
||||||
'Accept': '*/*',
|
'Accept': '*/*',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'User-Agent': 'HumeniusTSRankingBackend/0.0.1',
|
'User-Agent': 'HumeniusTSRankingBackend/0.0.2',
|
||||||
'Authorization': `Bearer ${bearerToken}`
|
'Authorization': `Bearer ${bearerToken}`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -134,7 +146,9 @@ export class SinusBotService {
|
|||||||
|
|
||||||
private checkStatus = response => {
|
private checkStatus = response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw Error(response.statusText);
|
let err = new RequestError(response.errorText);
|
||||||
|
err.response = response;
|
||||||
|
throw err;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ts-onlinetime-ranks",
|
"name": "ts-onlinetime-ranks-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "https://api.tsotr.humenius.me",
|
"proxy": "https://api.tsotr.humenius.me",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,46 +1,50 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import './App.scss';
|
import './App.scss';
|
||||||
import UserStatsMockService from "./mock/UserStatsMockService";
|
import UserStatsMockService from "./mock/UserStatsMockService";
|
||||||
import UserStats from "./models/TableEntry";
|
|
||||||
import UserStatsService from "./services/UserStatsService";
|
import UserStatsService from "./services/UserStatsService";
|
||||||
import {findDOMNode} from "react-dom";
|
import {findDOMNode} from "react-dom";
|
||||||
import TableEntry from "./models/TableEntry";
|
import TableEntry from "./models/TableEntry";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
error?: any,
|
error?: string,
|
||||||
isLoaded: boolean,
|
isLoaded: boolean,
|
||||||
users?: UserStats[],
|
users?: TableEntry[],
|
||||||
mock?: UserStats[]
|
mock?: TableEntry[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class App extends React.Component {
|
export default class App extends React.Component {
|
||||||
|
|
||||||
private apiService: UserStatsService = new UserStatsService();
|
private apiService: UserStatsService = new UserStatsService();
|
||||||
private mockService: UserStatsService = new UserStatsMockService();
|
private mockService = new UserStatsMockService();
|
||||||
|
|
||||||
state: State = {
|
state: State = {
|
||||||
error: null,
|
error: undefined,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
users: undefined,
|
users: undefined,
|
||||||
mock: undefined
|
mock: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.setState({isLoaded: false})
|
this.setState({isLoaded: false, mock: this.mockService.getStatsWithoutPromise()});
|
||||||
this.mockService.getStats()
|
// this.mockService.getStats()
|
||||||
.then(data => this.setState({
|
// .then(data => this.setState({
|
||||||
data: data,
|
// data: data,
|
||||||
mock: data
|
// mock: data
|
||||||
}));
|
// }))
|
||||||
|
// .finally(() => {
|
||||||
|
// });
|
||||||
this.apiService.getStats()
|
this.apiService.getStats()
|
||||||
.then(data => this.setState({
|
.then(data => this.setState({
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
users: data
|
users: data
|
||||||
}))
|
}))
|
||||||
.catch(error => this.setState({
|
.catch(error => {
|
||||||
isLoaded: true,
|
error.response.json()
|
||||||
error: error
|
.then((err: any) => this.setState({
|
||||||
}));
|
isLoaded: true,
|
||||||
|
error: err.error
|
||||||
|
}))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@@ -73,6 +77,8 @@ export default class App extends React.Component {
|
|||||||
return this.createTableEntries(users);
|
return this.createTableEntries(users);
|
||||||
} else if (isLoaded && error != null && mock != null) {
|
} else if (isLoaded && error != null && mock != null) {
|
||||||
return this.createTableEntries(mock);
|
return this.createTableEntries(mock);
|
||||||
|
} else if (mock != null) {
|
||||||
|
return this.createTableEntries(mock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +86,7 @@ export default class App extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<p className="title">Humenius' TeamSpeak 3-Ranking</p>
|
<p className="title">Humenius' TeamSpeak 3-Ranking</p>
|
||||||
{ this.state.error != null ? <p className="error-message">Data could not be loaded. Please try again later!</p> : null}
|
{ this.state.error != null ? <p className="error-message"> { this.state.error } Please try again later!</p> : null}
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -89,7 +95,7 @@ export default class App extends React.Component {
|
|||||||
<th>Online time</th>
|
<th>Online time</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className={this.state.error != null ? "blurred" : undefined}>
|
<tbody className={this.state.error != null || !this.state.isLoaded ? "blurred" : undefined}>
|
||||||
{this.renderTableData()}
|
{this.renderTableData()}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -19,4 +19,8 @@ export default class UserStatsMockService extends UserStatsService {
|
|||||||
async getStats(): Promise<TableEntry[]> {
|
async getStats(): Promise<TableEntry[]> {
|
||||||
return Promise.resolve(this.entries);
|
return Promise.resolve(this.entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getStatsWithoutPromise(): TableEntry[] {
|
||||||
|
return this.entries;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
3
frontend/src/models/RequestError.ts
Normal file
3
frontend/src/models/RequestError.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default class RequestError extends Error {
|
||||||
|
response?: any;
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
import TableEntry from "../models/TableEntry";
|
import TableEntry from "../models/TableEntry";
|
||||||
|
import RequestError from "../models/RequestError";
|
||||||
|
|
||||||
|
|
||||||
export default class UserStatsService {
|
export default class UserStatsService {
|
||||||
|
|
||||||
@@ -13,13 +15,16 @@ export default class UserStatsService {
|
|||||||
|
|
||||||
async getStats(): Promise<TableEntry[]> {
|
async getStats(): Promise<TableEntry[]> {
|
||||||
return fetch(this.apiURL, this.requestInit)
|
return fetch(this.apiURL, this.requestInit)
|
||||||
.then(res => this.checkResponse(res))
|
.then(res => UserStatsService.checkResponse(res))
|
||||||
.then(data => data.json())
|
.then(data => data.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkResponse(response: any): any {
|
private static checkResponse(response: any): any {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw Error(response.statusText);
|
console.log(response);
|
||||||
|
let error = new RequestError(response.statusText);
|
||||||
|
error.response = response;
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user