Database Connection Update #4
@@ -1,3 +1,3 @@
|
|||||||
export interface Predicate<T, O> {
|
export interface Predicate<T, O> {
|
||||||
process(input: T): O;
|
process(input: T): O;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
export class Sort {
|
export class Sort {
|
||||||
public static descending = (a: number, b: number) => {
|
public static descending = (a: number, b: number) => {
|
||||||
if (a < b) {
|
if (a < b) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
|
||||||
|
|
||||||
if (a > b) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (a > b) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
export class TimeUtil {
|
export class TimeUtil {
|
||||||
|
public static humanize = (seconds: number) => {
|
||||||
public static humanize = (seconds: number) => {
|
const d = Math.floor(seconds / (3600 * 24));
|
||||||
const d = Math.floor(seconds / (3600 * 24));
|
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
||||||
const h = Math.floor(seconds % (3600 * 24) / 3600);
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
const m = Math.floor(seconds % 3600 / 60);
|
const s = Math.floor(seconds % 60);
|
||||||
const s = Math.floor(seconds % 60);
|
|
||||||
|
return `${d}d ${h}h ${m}m ${s}s`;
|
||||||
return `${d}d ${h}h ${m}m ${s}s`;
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import { Controller, Get, HostParam, HttpException, HttpStatus, Param, Req } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HostParam,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Req,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
import { DatabaseService } from './database/database.service';
|
import { DatabaseService } from './database/database.service';
|
||||||
import logger from './logger/Logger';
|
import logger from './logger/Logger';
|
||||||
import { TableEntry } from "./models/TableEntry";
|
import { TableEntry } from './models/TableEntry';
|
||||||
import { SinusBotService } from "./services/sinusbot.service";
|
import { SinusBotService } from './services/sinusbot.service';
|
||||||
import { UserStatsResponse } from './models/aliases';
|
import { UserStatsResponse } from './models/aliases';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
@@ -11,7 +19,7 @@ export class AppController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly appService: AppService,
|
private readonly appService: AppService,
|
||||||
private readonly sinusBotService: SinusBotService,
|
private readonly sinusBotService: SinusBotService,
|
||||||
private readonly databaseService: DatabaseService
|
private readonly databaseService: DatabaseService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@@ -26,13 +34,16 @@ export class AppController {
|
|||||||
|
|
||||||
@Get('/stats/season/:id')
|
@Get('/stats/season/:id')
|
||||||
async getStats(@Param('id') id: string): Promise<UserStatsResponse> {
|
async getStats(@Param('id') id: string): Promise<UserStatsResponse> {
|
||||||
return this.databaseService.fetchStats(id)
|
return this.databaseService
|
||||||
.then(value => { return value; })
|
.fetchStats(id)
|
||||||
|
.then(value => {
|
||||||
|
return value;
|
||||||
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
logger.error(`Error occured when fetching stats.`, err);
|
logger.error(`Error occured when fetching stats.`, err);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'Error when fetching stats. Contact administrator or try again later!',
|
'Error when fetching stats. Contact administrator or try again later!',
|
||||||
HttpStatus.INTERNAL_SERVER_ERROR
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,13 @@ import { TimeTrackerPredicate } from './database/timetracking.predicate';
|
|||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService, SinusBotService, DatabaseService, PrismaService, TimeTrackerPredicate],
|
providers: [
|
||||||
|
AppService,
|
||||||
|
SinusBotService,
|
||||||
|
DatabaseService,
|
||||||
|
PrismaService,
|
||||||
|
TimeTrackerPredicate,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements NestModule {
|
export class AppModule implements NestModule {
|
||||||
configure(consumer: MiddlewareConsumer): any {
|
configure(consumer: MiddlewareConsumer): any {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TimeTrackerPredicate } from './timetracking.predicate';
|
import { TimeTrackerPredicate } from './timetracking.predicate';
|
||||||
import { SeasonInfo, TimeTrackerStats, UserStatsResponse } from '../models/aliases';
|
import {
|
||||||
|
SeasonInfo,
|
||||||
|
TimeTrackerStats,
|
||||||
|
UserStatsResponse,
|
||||||
|
} from '../models/aliases';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -16,8 +20,7 @@ export class DatabaseService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly prismaClient: PrismaService,
|
private readonly prismaClient: PrismaService,
|
||||||
private readonly timetrackerPredicate: TimeTrackerPredicate,
|
private readonly timetrackerPredicate: TimeTrackerPredicate,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
public fetchSeasonInfos = async (seasonId: string): Promise<SeasonInfo> => {
|
public fetchSeasonInfos = async (seasonId: string): Promise<SeasonInfo> => {
|
||||||
let seasons;
|
let seasons;
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
import { ranks, seasons, timetracker, user } from "@prisma/client";
|
import { ranks, seasons, timetracker, user } from '@prisma/client';
|
||||||
import { Predicate } from "src/api/predicate";
|
import { Predicate } from 'src/api/predicate';
|
||||||
import { Sort } from "src/api/sort";
|
import { Sort } from 'src/api/sort';
|
||||||
import { TimeUtil } from "src/api/timeutil";
|
import { TimeUtil } from 'src/api/timeutil';
|
||||||
import { TableEntry } from "src/models/TableEntry";
|
import { TableEntry } from 'src/models/TableEntry';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
type UserStats = timetracker & { user: user, ranks: ranks; };
|
type UserStats = timetracker & { user: user; ranks: ranks };
|
||||||
|
|
||||||
export class TimeTrackerPredicate implements Predicate<UserStats[], TableEntry[]> {
|
@Injectable()
|
||||||
|
export class TimeTrackerPredicate
|
||||||
process(input: UserStats[]): TableEntry[] {
|
implements Predicate<UserStats[], TableEntry[]> {
|
||||||
return input.filter((userStats: UserStats) => userStats.time != null)
|
process(input: UserStats[]): TableEntry[] {
|
||||||
.map((userStats: UserStats) => {
|
return input
|
||||||
return {
|
.filter((userStats: UserStats) => userStats.time != null)
|
||||||
name: userStats.user.name,
|
.map((userStats: UserStats) => {
|
||||||
rawTime: userStats.time,
|
return {
|
||||||
onlineTime: TimeUtil.humanize(userStats.time),
|
name: userStats.user.name,
|
||||||
rank: userStats.ranks.rank_name
|
rawTime: userStats.time,
|
||||||
}
|
onlineTime: TimeUtil.humanize(userStats.time),
|
||||||
})
|
rank: userStats.ranks.rank_name,
|
||||||
.sort((lhs, rhs) => Sort.descending(lhs.rawTime, rhs.rawTime));
|
};
|
||||||
}
|
})
|
||||||
}
|
.sort((lhs, rhs) => Sort.descending(lhs.rawTime, rhs.rawTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export class LoggerMiddleware implements NestMiddleware {
|
|||||||
const contentLength = response.get('content-length');
|
const contentLength = response.get('content-length');
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`${method} ${url} ${statusCode} ${contentLength} - ${userAgent} ${ip}`
|
`${method} ${url} ${statusCode} ${contentLength} - ${userAgent} ${ip}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import * as winston from 'winston';
|
import * as winston from 'winston';
|
||||||
|
|
||||||
const transports = {
|
const transports = {
|
||||||
console: new winston.transports.Console()
|
console: new winston.transports.Console(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
const logger = winston.createLogger({
|
||||||
transports: [transports.console]
|
transports: [transports.console],
|
||||||
})
|
});
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ interface RequestError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RequestErrorConstructor extends ErrorConstructor {
|
interface RequestErrorConstructor extends ErrorConstructor {
|
||||||
new(message?: string): RequestError;
|
new (message?: string): RequestError;
|
||||||
(message?: string): RequestError;
|
(message?: string): RequestError;
|
||||||
readonly prototype: RequestError;
|
readonly prototype: RequestError;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export interface TableEntry {
|
export interface TableEntry {
|
||||||
name: string;
|
name: string;
|
||||||
onlineTime: string;
|
rank: string;
|
||||||
|
onlineTime: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ranks, seasons, timetracker, user } from '@prisma/client';
|
import { ranks, seasons, timetracker, user } from '@prisma/client';
|
||||||
import { TableEntry } from './TableEntry';
|
import { TableEntry } from './TableEntry';
|
||||||
|
|
||||||
export type TimeTrackerStats = timetracker & { user: user; ranks: ranks; }
|
export type TimeTrackerStats = timetracker & { user: user; ranks: ranks };
|
||||||
export type SeasonInfo = seasons & { maxSeasonId: number }
|
export type SeasonInfo = seasons & { maxSeasonId: number };
|
||||||
export type UserStatsResponse = SeasonInfo & TableEntry[]
|
export type UserStatsResponse = SeasonInfo & TableEntry[];
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ export class PrismaService extends PrismaClient
|
|||||||
async onModuleDestroy() {
|
async onModuleDestroy() {
|
||||||
await this.$disconnect();
|
await this.$disconnect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import { TableEntry } from "../models/TableEntry";
|
import { TableEntry } from '../models/TableEntry';
|
||||||
import { TunakillUser } from "../models/TunakillUser";
|
import { TunakillUser } from '../models/TunakillUser';
|
||||||
import {HttpException, HttpStatus, Injectable} from "@nestjs/common";
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import {TunakillLogin} from "../models/TunakillLogin";
|
import { TunakillLogin } from '../models/TunakillLogin';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import logger from "../logger/Logger";
|
import logger from '../logger/Logger';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SinusBotService {
|
export class SinusBotService {
|
||||||
private host = process.env.HOST
|
private host = process.env.HOST;
|
||||||
private credentials = {
|
private credentials = {
|
||||||
username: process.env.SINUSBOT_USER,
|
username: process.env.SINUSBOT_USER,
|
||||||
password: process.env.SINUSBOT_PASSWORD
|
password: process.env.SINUSBOT_PASSWORD,
|
||||||
}
|
};
|
||||||
|
|
||||||
private botInfo = {
|
private botInfo = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
instanceId: process.env.SINUSBOT_INSTANCEID
|
instanceId: process.env.SINUSBOT_INSTANCEID,
|
||||||
}
|
};
|
||||||
|
|
||||||
private botIdURL = `${this.host}/api/v1/botId`;
|
private botIdURL = `${this.host}/api/v1/botId`;
|
||||||
private tunaKillURL = `${this.host}/api/v1/bot/i/${this.botInfo.instanceId}/event/tunakill_rank_all_user`;
|
private tunaKillURL = `${this.host}/api/v1/bot/i/${this.botInfo.instanceId}/event/tunakill_rank_all_user`;
|
||||||
@@ -34,23 +34,30 @@ export class SinusBotService {
|
|||||||
// if (this.bearer == null) {
|
// if (this.bearer == null) {
|
||||||
logger.info(`Hey! I'm trying to get my Bearer token!`);
|
logger.info(`Hey! I'm trying to get my Bearer token!`);
|
||||||
await this.login()
|
await this.login()
|
||||||
.then(token => this.bearer = token)
|
.then(token => (this.bearer = token))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Seems like I have my Bearer token!
|
`Seems like I have my Bearer token!
|
||||||
Looks like it's ${this.bearer == null ? 'undefined' : 'not undefined'}`
|
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))
|
||||||
.then(res => this.checkStatus(res))
|
.then(res => this.checkStatus(res))
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.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(HttpStatus.INTERNAL_SERVER_ERROR, error.message);
|
throw this.createHttpException(
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
error.message,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +68,15 @@ export class SinusBotService {
|
|||||||
if (this.botInfo.id == null) {
|
if (this.botInfo.id == null) {
|
||||||
logger.debug(`I have to fetch a bot ID before I can continue!`);
|
logger.debug(`I have to fetch a bot ID before I can continue!`);
|
||||||
await this.fetchDefaultBotId()
|
await this.fetchDefaultBotId()
|
||||||
.then(defaultBotId => this.botInfo.id = defaultBotId)
|
.then(defaultBotId => (this.botInfo.id = defaultBotId))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
logger.warn(`I couldn't retrieve SinusBot bot information. Login is likely to fail!`, error);
|
logger.warn(
|
||||||
|
`I couldn't retrieve SinusBot bot information. Login is likely to fail!`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw this.createHttpException(
|
throw this.createHttpException(
|
||||||
HttpStatus.NOT_FOUND,
|
HttpStatus.NOT_FOUND,
|
||||||
`Could not fetch enough bot information for further requests.`
|
`Could not fetch enough bot information for further requests.`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
logger.info(`The bot ID now is ${this.botInfo.id}`);
|
logger.info(`The bot ID now is ${this.botInfo.id}`);
|
||||||
@@ -75,20 +85,23 @@ export class SinusBotService {
|
|||||||
const body: TunakillLogin = {
|
const body: TunakillLogin = {
|
||||||
username: this.credentials.username,
|
username: this.credentials.username,
|
||||||
password: this.credentials.password,
|
password: this.credentials.password,
|
||||||
botId: this.botInfo.id
|
botId: this.botInfo.id,
|
||||||
}
|
};
|
||||||
|
|
||||||
logger.info(`Logging in for Bearer token!`)
|
logger.info(`Logging in for Bearer token!`);
|
||||||
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 => {
|
.catch(error => {
|
||||||
logger.error(`Oh oh! Something went wrong while fetching Bearer token.`, error);
|
logger.error(
|
||||||
|
`Oh oh! Something went wrong while fetching Bearer token.`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
throw this.createHttpException(
|
throw this.createHttpException(
|
||||||
HttpStatus.UNAUTHORIZED,
|
HttpStatus.UNAUTHORIZED,
|
||||||
`Fetching Bearer token for Sinusbot failed.
|
`Fetching Bearer token for Sinusbot failed.
|
||||||
Please refresh page or try again later!`
|
Please refresh page or try again later!`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -110,39 +123,56 @@ export class SinusBotService {
|
|||||||
throw Error('Response from SinusBot does not have any data to parse.');
|
throw Error('Response from SinusBot does not have any data to parse.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return (
|
||||||
// TODO: Remove hardcoded username filter for bots.
|
response
|
||||||
.filter((user: TunakillUser) => user.name !== "Server Query Admin" && user.name !== "DJ Inshalla")
|
// TODO: Remove hardcoded username filter for bots.
|
||||||
|
.filter(
|
||||||
|
(user: TunakillUser) =>
|
||||||
|
user.name !== 'Server Query Admin' && user.name !== 'DJ Inshalla',
|
||||||
|
)
|
||||||
|
|
||||||
.filter((user: TunakillUser) => user.time != null)
|
.filter((user: TunakillUser) => user.time != null)
|
||||||
.map((user: TunakillUser) => {
|
.map((user: TunakillUser) => {
|
||||||
return {
|
return {
|
||||||
name: user.name,
|
name: user.name,
|
||||||
rawTime: user.time,
|
rawTime: user.time,
|
||||||
onlineTime: this.humanizeTime(user.time)
|
onlineTime: this.humanizeTime(user.time),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort((a: any, b: any) => this.sortByDescendingTime(a.rawTime, b.rawTime));
|
.sort((a: any, b: any) =>
|
||||||
|
this.sortByDescendingTime(a.rawTime, b.rawTime),
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
private requestConfig = (body?: string, bearerToken?: string, requestType: string = 'POST'): RequestInit => {
|
private requestConfig = (
|
||||||
|
body?: string,
|
||||||
|
bearerToken?: string,
|
||||||
|
requestType: string = 'POST',
|
||||||
|
): RequestInit => {
|
||||||
return {
|
return {
|
||||||
method: requestType,
|
method: requestType,
|
||||||
body: body,
|
body: body,
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': '*/*',
|
Accept: '*/*',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'User-Agent': 'HumeniusTSRankingBackend/0.0.2',
|
'User-Agent': 'HumeniusTSRankingBackend/0.0.2',
|
||||||
'Authorization': `Bearer ${bearerToken}`
|
Authorization: `Bearer ${bearerToken}`,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
private createHttpException = (statusCode: HttpStatus, message?: string): HttpException => {
|
private createHttpException = (
|
||||||
return new HttpException({
|
statusCode: HttpStatus,
|
||||||
status: statusCode,
|
message?: string,
|
||||||
error: message
|
): HttpException => {
|
||||||
}, statusCode);
|
return new HttpException(
|
||||||
}
|
{
|
||||||
|
status: statusCode,
|
||||||
|
error: message,
|
||||||
|
},
|
||||||
|
statusCode,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private checkStatus = response => {
|
private checkStatus = response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -151,7 +181,7 @@ export class SinusBotService {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
}
|
};
|
||||||
|
|
||||||
private sortByDescendingTime = (a: number, b: number) => {
|
private sortByDescendingTime = (a: number, b: number) => {
|
||||||
if (a < b) {
|
if (a < b) {
|
||||||
@@ -163,16 +193,16 @@ export class SinusBotService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
};
|
||||||
|
|
||||||
private humanizeTime = (seconds: number) => {
|
private humanizeTime = (seconds: number) => {
|
||||||
const d = Math.floor(seconds / (3600 * 24));
|
const d = Math.floor(seconds / (3600 * 24));
|
||||||
const h = Math.floor(seconds % (3600 * 24) / 3600);
|
const h = Math.floor((seconds % (3600 * 24)) / 3600);
|
||||||
const m = Math.floor(seconds % 3600 / 60);
|
const m = Math.floor((seconds % 3600) / 60);
|
||||||
const s = Math.floor(seconds % 60);
|
const s = Math.floor(seconds % 60);
|
||||||
|
|
||||||
return `${d}d ${h}h ${m}m ${s}s`;
|
return `${d}d ${h}h ${m}m ${s}s`;
|
||||||
}
|
};
|
||||||
|
|
||||||
private static logResponse(res: any) {
|
private static logResponse(res: any) {
|
||||||
logger.debug(res);
|
logger.debug(res);
|
||||||
|
|||||||
Reference in New Issue
Block a user