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
|
||||
type: docker
|
||||
name: default
|
||||
name: frontend
|
||||
|
||||
steps:
|
||||
- name: Build and push frontend image
|
||||
@@ -14,7 +14,18 @@ steps:
|
||||
context: frontend/
|
||||
repo: docker.humenius.me/humenius/ts-onlinetime-ranks-frontend
|
||||
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
|
||||
image: plugins/docker
|
||||
settings:
|
||||
@@ -26,4 +37,8 @@ steps:
|
||||
context: backend/
|
||||
repo: docker.humenius.me/humenius/ts-onlinetime-ranks-backend
|
||||
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
|
||||
COPY --from=builder /app .
|
||||
|
||||
EXPOSE 3000
|
||||
EXPOSE 3500
|
||||
CMD ["npm", "run", "start:prod"]
|
||||
|
||||
|
||||
@@ -7,6 +7,6 @@ COPY package-lock.json .
|
||||
RUN npm install
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
EXPOSE 3500
|
||||
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==",
|
||||
"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": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
@@ -4565,6 +4575,13 @@
|
||||
"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": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@@ -7360,9 +7377,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
@@ -7856,6 +7873,13 @@
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"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": {
|
||||
"version": "1.2.13",
|
||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||
@@ -10379,9 +10403,9 @@
|
||||
"integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw=="
|
||||
},
|
||||
"ts-jest": {
|
||||
"version": "25.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-25.2.1.tgz",
|
||||
"integrity": "sha512-TnntkEEjuXq/Gxpw7xToarmHbAafgCaAzOpnajnFC6jI7oo1trMzAHA04eWpc3MhV6+yvhE8uUBAmN+teRJh0A==",
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.1.1.tgz",
|
||||
"integrity": "sha512-Lk/357quLg5jJFyBQLnSbhycnB3FPe+e9i7ahxokyXxAYoB0q1pPmqxxRPYr4smJic1Rjcf7MXDBhZWgxlli0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bs-logger": "0.x",
|
||||
@@ -10390,10 +10414,10 @@
|
||||
"json5": "2.x",
|
||||
"lodash.memoize": "4.x",
|
||||
"make-error": "1.x",
|
||||
"mkdirp": "0.x",
|
||||
"resolve": "1.x",
|
||||
"semver": "^5.5",
|
||||
"yargs-parser": "^16.1.0"
|
||||
"micromatch": "4.x",
|
||||
"mkdirp": "1.x",
|
||||
"semver": "7.x",
|
||||
"yargs-parser": "18.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
@@ -10405,15 +10429,27 @@
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "16.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz",
|
||||
"integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==",
|
||||
"micromatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
|
||||
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
"braces": "^3.0.1",
|
||||
"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",
|
||||
"integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "3.1.0",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"name": "ts-onlinetime-ranks-backend",
|
||||
"version": "0.0.2",
|
||||
"description": "Backend microservice for TeamSpeak 3 Online Time Ranking",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"scripts": {
|
||||
@@ -46,7 +45,7 @@
|
||||
"jest": "^25.1.0",
|
||||
"prettier": "^1.19.1",
|
||||
"supertest": "^4.0.2",
|
||||
"ts-jest": "25.2.1",
|
||||
"ts-jest": "^26.1.1",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
|
||||
@@ -4,6 +4,6 @@ import { AppModule } from './app.module';
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.enableCors();
|
||||
await app.listen(3000);
|
||||
await app.listen(3500);
|
||||
}
|
||||
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;
|
||||
|
||||
public async fetchStats(): Promise<TableEntry[]> {
|
||||
if (this.bearer == null) {
|
||||
logger.info(`Hey! I'm trying to get my Bearer token!`);
|
||||
await this.login()
|
||||
.then(token => this.bearer = token)
|
||||
.catch(error => {
|
||||
logger.error(`Oh oh! Something went wrong while fetching Bearer token.`, error);
|
||||
throw this.createHttpException(HttpStatus.UNAUTHORIZED, error.message);
|
||||
});
|
||||
logger.debug(
|
||||
`Seems like I have my Bearer token!
|
||||
Looks like it's ${this.bearer == null ? 'undefined' : 'not undefined'}`
|
||||
);
|
||||
}
|
||||
// Skip check as either way
|
||||
// - An interval needs to reset this.bearer to null
|
||||
// - The Sinusbot token is not a JWT => Expiration date is not decodable
|
||||
// - Estimated expiration time: 1d?
|
||||
// - I don't know if it makes a difference to check via interval or to just fetch the token
|
||||
// everytime a request is sent against this API.
|
||||
// if (this.bearer == null) {
|
||||
logger.info(`Hey! I'm trying to get my Bearer token!`);
|
||||
await this.login()
|
||||
.then(token => this.bearer = token)
|
||||
.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}`);
|
||||
return await fetch(this.tunaKillURL, this.requestConfig(null, this.bearer))
|
||||
@@ -46,7 +50,7 @@ export class SinusBotService {
|
||||
.then(data => this.consumeTunakillResponse(data))
|
||||
.catch(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)))
|
||||
.then(res => this.checkStatus(res))
|
||||
.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> {
|
||||
@@ -90,12 +102,12 @@ export class SinusBotService {
|
||||
|
||||
private consumeTunakillResponse(data: any): TableEntry[] {
|
||||
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;
|
||||
if (!(response.length > 0)) {
|
||||
throw Error('User list is empty.')
|
||||
throw Error('Response from SinusBot does not have any data to parse.');
|
||||
}
|
||||
|
||||
return response
|
||||
@@ -119,7 +131,7 @@ export class SinusBotService {
|
||||
headers: {
|
||||
'Accept': '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'HumeniusTSRankingBackend/0.0.1',
|
||||
'User-Agent': 'HumeniusTSRankingBackend/0.0.2',
|
||||
'Authorization': `Bearer ${bearerToken}`
|
||||
}
|
||||
};
|
||||
@@ -134,7 +146,9 @@ export class SinusBotService {
|
||||
|
||||
private checkStatus = response => {
|
||||
if (!response.ok) {
|
||||
throw Error(response.statusText);
|
||||
let err = new RequestError(response.errorText);
|
||||
err.response = response;
|
||||
throw err;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ts-onlinetime-ranks",
|
||||
"version": "0.1.0",
|
||||
"name": "ts-onlinetime-ranks-frontend",
|
||||
"version": "0.0.2",
|
||||
"private": true,
|
||||
"proxy": "https://api.tsotr.humenius.me",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,46 +1,50 @@
|
||||
import React from 'react';
|
||||
import './App.scss';
|
||||
import UserStatsMockService from "./mock/UserStatsMockService";
|
||||
import UserStats from "./models/TableEntry";
|
||||
import UserStatsService from "./services/UserStatsService";
|
||||
import {findDOMNode} from "react-dom";
|
||||
import TableEntry from "./models/TableEntry";
|
||||
|
||||
interface State {
|
||||
error?: any,
|
||||
error?: string,
|
||||
isLoaded: boolean,
|
||||
users?: UserStats[],
|
||||
mock?: UserStats[]
|
||||
users?: TableEntry[],
|
||||
mock?: TableEntry[]
|
||||
}
|
||||
|
||||
export default class App extends React.Component {
|
||||
|
||||
private apiService: UserStatsService = new UserStatsService();
|
||||
private mockService: UserStatsService = new UserStatsMockService();
|
||||
private mockService = new UserStatsMockService();
|
||||
|
||||
state: State = {
|
||||
error: null,
|
||||
error: undefined,
|
||||
isLoaded: false,
|
||||
users: undefined,
|
||||
mock: undefined
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({isLoaded: false})
|
||||
this.mockService.getStats()
|
||||
.then(data => this.setState({
|
||||
data: data,
|
||||
mock: data
|
||||
}));
|
||||
this.setState({isLoaded: false, mock: this.mockService.getStatsWithoutPromise()});
|
||||
// this.mockService.getStats()
|
||||
// .then(data => this.setState({
|
||||
// data: data,
|
||||
// mock: data
|
||||
// }))
|
||||
// .finally(() => {
|
||||
// });
|
||||
this.apiService.getStats()
|
||||
.then(data => this.setState({
|
||||
isLoaded: true,
|
||||
users: data
|
||||
}))
|
||||
.catch(error => this.setState({
|
||||
isLoaded: true,
|
||||
error: error
|
||||
}));
|
||||
.catch(error => {
|
||||
error.response.json()
|
||||
.then((err: any) => this.setState({
|
||||
isLoaded: true,
|
||||
error: err.error
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
@@ -73,6 +77,8 @@ export default class App extends React.Component {
|
||||
return this.createTableEntries(users);
|
||||
} else if (isLoaded && error != null && mock != null) {
|
||||
return this.createTableEntries(mock);
|
||||
} else if (mock != null) {
|
||||
return this.createTableEntries(mock);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +86,7 @@ export default class App extends React.Component {
|
||||
return (
|
||||
<div className="App">
|
||||
<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>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -89,7 +95,7 @@ export default class App extends React.Component {
|
||||
<th>Online time</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className={this.state.error != null ? "blurred" : undefined}>
|
||||
<tbody className={this.state.error != null || !this.state.isLoaded ? "blurred" : undefined}>
|
||||
{this.renderTableData()}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -19,4 +19,8 @@ export default class UserStatsMockService extends UserStatsService {
|
||||
async getStats(): Promise<TableEntry[]> {
|
||||
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 RequestError from "../models/RequestError";
|
||||
|
||||
|
||||
export default class UserStatsService {
|
||||
|
||||
@@ -13,13 +15,16 @@ export default class UserStatsService {
|
||||
|
||||
async getStats(): Promise<TableEntry[]> {
|
||||
return fetch(this.apiURL, this.requestInit)
|
||||
.then(res => this.checkResponse(res))
|
||||
.then(data => data.json())
|
||||
.then(res => UserStatsService.checkResponse(res))
|
||||
.then(data => data.json());
|
||||
}
|
||||
|
||||
private checkResponse(response: any): any {
|
||||
private static checkResponse(response: any): any {
|
||||
if (!response.ok) {
|
||||
throw Error(response.statusText);
|
||||
console.log(response);
|
||||
let error = new RequestError(response.statusText);
|
||||
error.response = response;
|
||||
throw error;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user