mirror of
https://github.com/TheodoreKrypton/tgfs
synced 2024-11-21 22:55:40 +00:00
.
This commit is contained in:
parent
335f98587e
commit
0d5c9c974d
@ -19,7 +19,7 @@
|
||||
"input": "^1.0.1",
|
||||
"ip": "^1.1.9",
|
||||
"js-yaml": "^4.1.0",
|
||||
"json-web-token": "^3.2.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"telegraf": "^4.15.0",
|
||||
"telegram": "^2.18.38",
|
||||
"uuid": "^9.0.0",
|
||||
@ -32,6 +32,7 @@
|
||||
"@types/ip": "^1.1.0",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsonwebtoken": "^9.0.6",
|
||||
"@types/node": "^20.3.3",
|
||||
"@types/supertest": "^2.0.12",
|
||||
"@types/uuid": "^9.0.2",
|
||||
|
@ -5,7 +5,7 @@ import { IBot, TDLibApi } from 'src/api/interface';
|
||||
import { config } from 'src/config';
|
||||
import { TechnicalError } from 'src/errors/base';
|
||||
import { MessageNotFound } from 'src/errors/telegram';
|
||||
import { db } from 'src/server/manager/db';
|
||||
import { manager } from 'src/server/manager';
|
||||
import { Logger } from 'src/utils/logger';
|
||||
|
||||
import { getUploader } from './file-uploader';
|
||||
@ -164,20 +164,28 @@ export class MessageApi extends MessageBroker {
|
||||
name: string,
|
||||
messageId: number,
|
||||
): AsyncGenerator<Buffer> {
|
||||
const task = db.createTask(name, 0, 'download');
|
||||
|
||||
let downloaded = 0;
|
||||
|
||||
for await (const buffer of this.tdlib.account.downloadFile({
|
||||
const { chunks, size } = await this.tdlib.account.downloadFile({
|
||||
chatId: this.privateChannelId,
|
||||
messageId: messageId,
|
||||
chunkSize: config.tgfs.download.chunk_size_kb,
|
||||
})) {
|
||||
yield buffer;
|
||||
downloaded += buffer.length;
|
||||
task.reportProgress(downloaded);
|
||||
}
|
||||
});
|
||||
|
||||
task.finish();
|
||||
const task = manager.createDownloadTask(name, size);
|
||||
task.begin();
|
||||
|
||||
try {
|
||||
for await (const buffer of chunks) {
|
||||
yield buffer;
|
||||
downloaded += buffer.length;
|
||||
task.reportProgress(downloaded);
|
||||
}
|
||||
} catch (err) {
|
||||
task.setErrors([err]);
|
||||
throw err;
|
||||
} finally {
|
||||
task.complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { SendMessageResp, UploadedFile } from 'src/api/types';
|
||||
import { Queue, generateFileId, getAppropriatedPartSize } from 'src/api/utils';
|
||||
import { AggregatedError, TechnicalError } from 'src/errors/base';
|
||||
import { FileTooBig } from 'src/errors/telegram';
|
||||
import { manager } from 'src/server/manager';
|
||||
import { Logger } from 'src/utils/logger';
|
||||
|
||||
import {
|
||||
@ -125,6 +126,7 @@ export abstract class FileUploader<T extends GeneralFileMessage> {
|
||||
big: 15,
|
||||
},
|
||||
): Promise<void> {
|
||||
const task = manager.createUploadTask(this.fileName, this.fileSize);
|
||||
this.prepare(file);
|
||||
try {
|
||||
this.fileName = fileName ?? this.defaultFileName;
|
||||
@ -159,6 +161,9 @@ export abstract class FileUploader<T extends GeneralFileMessage> {
|
||||
await Promise.all(promises);
|
||||
} finally {
|
||||
this.close();
|
||||
|
||||
task.errors = this.errors;
|
||||
task.complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,9 +195,9 @@ export class GramJSApi implements ITDLibClient {
|
||||
};
|
||||
}
|
||||
|
||||
public async *downloadFile(
|
||||
public async downloadFile(
|
||||
req: types.DownloadFileReq,
|
||||
): types.DownloadFileResp {
|
||||
): Promise<types.DownloadFileResp> {
|
||||
const message = (
|
||||
await this.getMessages({
|
||||
chatId: req.chatId,
|
||||
@ -208,17 +208,27 @@ export class GramJSApi implements ITDLibClient {
|
||||
const chunkSize = req.chunkSize * 1024;
|
||||
|
||||
let i = 0;
|
||||
for await (const chunk of this.client.iterDownload({
|
||||
file: new Api.InputDocumentFileLocation({
|
||||
id: message.document.id,
|
||||
accessHash: message.document.accessHash,
|
||||
fileReference: message.document.fileReference,
|
||||
thumbSize: '',
|
||||
}),
|
||||
requestSize: chunkSize,
|
||||
})) {
|
||||
i += 1;
|
||||
yield Buffer.from(chunk);
|
||||
|
||||
const client = this.client;
|
||||
|
||||
async function* chunks() {
|
||||
for await (const chunk of client.iterDownload({
|
||||
file: new Api.InputDocumentFileLocation({
|
||||
id: message.document.id,
|
||||
accessHash: message.document.accessHash,
|
||||
fileReference: message.document.fileReference,
|
||||
thumbSize: '',
|
||||
}),
|
||||
requestSize: chunkSize,
|
||||
})) {
|
||||
i += 1;
|
||||
yield Buffer.from(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
chunks: chunks(),
|
||||
size: Number(message.document.size),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ export interface ITDLibClient {
|
||||
|
||||
sendSmallFile(req: types.SendFileReq): Promise<types.SendMessageResp>;
|
||||
|
||||
downloadFile(req: types.DownloadFileReq): types.DownloadFileResp;
|
||||
downloadFile(req: types.DownloadFileReq): Promise<types.DownloadFileResp>;
|
||||
}
|
||||
|
||||
export interface IBot {
|
||||
|
@ -83,4 +83,7 @@ export type DownloadFileReq = Chat &
|
||||
chunkSize: number;
|
||||
};
|
||||
|
||||
export type DownloadFileResp = AsyncGenerator<Buffer>;
|
||||
export type DownloadFileResp = {
|
||||
chunks: AsyncGenerator<Buffer>;
|
||||
size: number;
|
||||
};
|
||||
|
@ -45,7 +45,7 @@ export type Config = {
|
||||
jwt: {
|
||||
secret: string;
|
||||
algorithm: string;
|
||||
expiration: number;
|
||||
life: number;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -108,7 +108,7 @@ export const loadConfig = (configPath: string): Config => {
|
||||
jwt: {
|
||||
secret: cfg['manager']['jwt']['secret'],
|
||||
algorithm: cfg['manager']['jwt']['algorithm'] ?? 'HS256',
|
||||
expiration: cfg['manager']['jwt']['expiration'],
|
||||
life: cfg['manager']['jwt']['life'],
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -212,7 +212,7 @@ export const createConfig = async (): Promise<string> => {
|
||||
jwt: {
|
||||
secret: generateRandomSecret(),
|
||||
algorithm: 'HS256',
|
||||
expiration: 3600 * 24 * 7,
|
||||
life: 3600 * 24 * 7,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -36,7 +36,7 @@ export class IncorrectPassword extends InvalidCredentials {
|
||||
}
|
||||
|
||||
export class JWTInvalid extends InvalidCredentials {
|
||||
constructor() {
|
||||
super('JWT token invalid');
|
||||
constructor(public readonly cause: string) {
|
||||
super(`JWT token invalid, ${cause}`);
|
||||
}
|
||||
}
|
||||
|
22
src/index.ts
22
src/index.ts
@ -92,16 +92,18 @@ const { argv }: any = yargs(hideBin(process.argv))
|
||||
}
|
||||
}
|
||||
|
||||
startServer(
|
||||
'Manager',
|
||||
(req, res, next) => {
|
||||
managerServer(req, res);
|
||||
next();
|
||||
},
|
||||
config.manager.host,
|
||||
config.manager.port,
|
||||
config.manager.path,
|
||||
);
|
||||
managerServer.listen(config.manager.port, config.manager.host);
|
||||
|
||||
// startServer(
|
||||
// 'Manager',
|
||||
// (req, res, next) => {
|
||||
// managerServer(req, res);
|
||||
// next();
|
||||
// },
|
||||
// config.manager.host,
|
||||
// config.manager.port,
|
||||
// config.manager.path,
|
||||
// );
|
||||
|
||||
startBot();
|
||||
})();
|
||||
|
@ -1,48 +1,94 @@
|
||||
import jwt from 'json-web-token';
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import { config } from 'src/config';
|
||||
import { IncorrectPassword, JWTInvalid, UserNotFound } from 'src/errors';
|
||||
import { TechnicalError } from 'src/errors/base';
|
||||
|
||||
const sha256 = async (s: string) => {
|
||||
const msgBuffer = new TextEncoder().encode(s);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
return hashHex;
|
||||
};
|
||||
import { Context, WithContext } from './model/context';
|
||||
import { LoggerWithContext } from './utils/logger';
|
||||
|
||||
type JWTPayload = {
|
||||
username: string;
|
||||
user: string;
|
||||
exp: number;
|
||||
iat: number;
|
||||
};
|
||||
|
||||
export const generateToken = async (username: string, password: string) => {
|
||||
if (config.tgfs.users[username] === undefined) {
|
||||
throw new UserNotFound(username);
|
||||
}
|
||||
if ((await sha256(config.tgfs.users[username].password)) !== password) {
|
||||
// the password sent in is sha256 hashed
|
||||
throw new IncorrectPassword(username);
|
||||
}
|
||||
return jwt.encode;
|
||||
};
|
||||
export class Auth extends WithContext {
|
||||
private logger: LoggerWithContext;
|
||||
|
||||
export const verifyToken = (token: string): Promise<JWTPayload> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const payload = jwt.decode(
|
||||
config.manager.jwt.secret,
|
||||
token,
|
||||
(error, payload) => {
|
||||
if (error) {
|
||||
reject(new JWTInvalid());
|
||||
}
|
||||
},
|
||||
);
|
||||
if (payload.exp < Date.now()) {
|
||||
reject(new JWTInvalid());
|
||||
constructor(protected readonly context: Context) {
|
||||
super(context);
|
||||
|
||||
this.logger = new LoggerWithContext(context);
|
||||
}
|
||||
|
||||
private static sha256 = async (s: string) => {
|
||||
const msgBuffer = new TextEncoder().encode(s);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
return hashHex;
|
||||
};
|
||||
|
||||
public async generateToken(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<string> {
|
||||
if (config.tgfs.users[username] === undefined) {
|
||||
throw new UserNotFound(username);
|
||||
}
|
||||
resolve(payload as JWTPayload);
|
||||
});
|
||||
};
|
||||
if (
|
||||
(await Auth.sha256(config.tgfs.users[username].password)) !== password
|
||||
) {
|
||||
// the password sent in is sha256 hashed
|
||||
throw new IncorrectPassword(username);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.sign(
|
||||
{
|
||||
user: username,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
},
|
||||
config.manager.jwt.secret,
|
||||
{
|
||||
issuer: 'tgfs-manager',
|
||||
audience: 'tgfs-manager',
|
||||
subject: username,
|
||||
expiresIn: config.manager.jwt.life,
|
||||
algorithm: config.manager.jwt.algorithm,
|
||||
} as jwt.SignOptions,
|
||||
(error, token) => {
|
||||
if (error) {
|
||||
this.logger.error(error);
|
||||
reject(new TechnicalError('token generation failed'));
|
||||
} else {
|
||||
this.context.user = username;
|
||||
this.logger.info(`jwt token issued`);
|
||||
resolve(token);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public async authenticate(token: string): Promise<JWTPayload> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const payload = jwt.verify(
|
||||
config.manager.jwt.secret,
|
||||
token,
|
||||
(error, decoded: JWTPayload) => {
|
||||
if (error) {
|
||||
this.logger.error(error);
|
||||
reject(new JWTInvalid('verification failed'));
|
||||
} else {
|
||||
this.context.user = decoded.user;
|
||||
resolve(decoded);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,11 @@ class Database {
|
||||
this.uploadTasks = new Map();
|
||||
}
|
||||
|
||||
createTask(fileName: string, totalSize: number, type: 'download' | 'upload') {
|
||||
private createTask(
|
||||
fileName: string,
|
||||
totalSize: number,
|
||||
type: 'download' | 'upload',
|
||||
): Task {
|
||||
const task = new Task(fileName, totalSize, type);
|
||||
if (type === 'download') {
|
||||
this.downloadTasks[task.id] = task;
|
||||
@ -20,6 +24,14 @@ class Database {
|
||||
return task;
|
||||
}
|
||||
|
||||
createUploadTask(fileName: string, totalSize: number): Task {
|
||||
return this.createTask(fileName, totalSize, 'upload');
|
||||
}
|
||||
|
||||
createDownloadTask(fileName: string, totalSize: number): Task {
|
||||
return this.createTask(fileName, totalSize, 'download');
|
||||
}
|
||||
|
||||
getTasks() {
|
||||
return {
|
||||
download: this.downloadTasks,
|
||||
@ -28,4 +40,4 @@ class Database {
|
||||
}
|
||||
}
|
||||
|
||||
export const db = new Database();
|
||||
export const manager = new Database();
|
||||
|
@ -1,54 +1,114 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
import express, { Response } from 'express';
|
||||
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { BadAuthentication } from 'src/errors';
|
||||
import { TechnicalError } from 'src/errors/base';
|
||||
|
||||
import { generateToken, verifyToken } from './auth';
|
||||
import { db } from './db';
|
||||
import { Auth } from './auth';
|
||||
import { manager } from './db';
|
||||
import { Request } from './model/request';
|
||||
import { Logger } from './utils/logger';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
if (req.path === '/login') {
|
||||
next();
|
||||
}
|
||||
const autoCatch = (
|
||||
fn: (
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: (err?: Error) => void,
|
||||
) => void | Promise<void>,
|
||||
) => {
|
||||
return async (req: Request, res: Response, next: (err?: Error) => void) => {
|
||||
try {
|
||||
await fn(req, res, next);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const token = req.headers['authorization'];
|
||||
if (token === undefined) {
|
||||
res.redirect('/');
|
||||
}
|
||||
app.use(express.json());
|
||||
|
||||
try {
|
||||
await verifyToken(token);
|
||||
} catch (err) {
|
||||
res.redirect('/');
|
||||
}
|
||||
app.use((req: Request & { reqId: string }, res, next) => {
|
||||
req.id = uuid();
|
||||
req.logger = Logger.ctx(req);
|
||||
req.logger.info(req.method, req.path);
|
||||
req.auth = new Auth(req);
|
||||
next();
|
||||
});
|
||||
|
||||
// set cors headers
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Methods', 'DELETE, POST, GET, OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', 'content-type, authorization');
|
||||
res.header('Access-Control-Allow-Credentials', 'true');
|
||||
next();
|
||||
});
|
||||
|
||||
app.post('/login', (req, res) => {
|
||||
const token = generateToken(req.body.username, req.body.password);
|
||||
res.setHeader('Set-Cookie', `token=${token}; HttpOnly`);
|
||||
res.end();
|
||||
app.use(
|
||||
autoCatch(async (req: Request, res, next) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
next();
|
||||
} else if (req.method === 'POST' && req.path === '/login') {
|
||||
next();
|
||||
} else {
|
||||
const token = req.headers.authorization.replace('Bearer ', '');
|
||||
req.auth = new Auth(req);
|
||||
await req.auth.authenticate(token);
|
||||
next();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
app.options('*', (req, res, next) => {
|
||||
res.status(200);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/tasks', (req, res) => {
|
||||
console.log(req.headers);
|
||||
res.send(db.getTasks());
|
||||
});
|
||||
app.post(
|
||||
'/login',
|
||||
autoCatch(async (req, res, next) => {
|
||||
const token = await req.auth.generateToken(
|
||||
req.body.username,
|
||||
req.body.password,
|
||||
);
|
||||
res.write(token);
|
||||
next();
|
||||
}),
|
||||
);
|
||||
|
||||
app.use((err: Error, req: Request, res: Response, next: () => any) => {
|
||||
if (err instanceof TechnicalError) {
|
||||
const error = new err.httpError(err.message);
|
||||
res.status(error.statusCode).send(error.message);
|
||||
} else {
|
||||
res.status(500).send('Internal Server Error');
|
||||
}
|
||||
app.get(
|
||||
'/tasks',
|
||||
autoCatch(async (req, res, next) => {
|
||||
res.write(manager.getTasks());
|
||||
next();
|
||||
}),
|
||||
);
|
||||
|
||||
app.use(
|
||||
(err: Error, req: Request, res: Response, next: (err?: Error) => void) => {
|
||||
req.logger.error(err);
|
||||
next(err);
|
||||
},
|
||||
);
|
||||
|
||||
app.use(
|
||||
(err: Error, req: Request, res: Response, next: (err?: Error) => void) => {
|
||||
if (err instanceof TechnicalError) {
|
||||
const error = new err.httpError(err.message);
|
||||
res.status(error.statusCode);
|
||||
} else {
|
||||
res.status(500);
|
||||
}
|
||||
next();
|
||||
},
|
||||
);
|
||||
|
||||
app.use((req: Request, res) => {
|
||||
req.logger.info(res.statusCode);
|
||||
res.send();
|
||||
});
|
||||
|
||||
export const managerServer = app;
|
||||
T
|
@ -1,2 +1,3 @@
|
||||
export { startBot } from './bot';
|
||||
export { manager } from './db';
|
||||
export { managerServer } from './http-server';
|
||||
|
8
src/server/manager/model/context.ts
Normal file
8
src/server/manager/model/context.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export type Context = {
|
||||
id: string;
|
||||
user?: string;
|
||||
};
|
||||
|
||||
export class WithContext {
|
||||
constructor(protected readonly context: Context) {}
|
||||
}
|
13
src/server/manager/model/request.ts
Normal file
13
src/server/manager/model/request.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Request as _Request } from 'express';
|
||||
|
||||
import { Auth } from 'src/server/manager/auth';
|
||||
import { Logger, LoggerWithContext } from 'src/server/manager/utils/logger';
|
||||
|
||||
import { Context } from './context';
|
||||
|
||||
export type Request = _Request &
|
||||
Context & {
|
||||
logger?: LoggerWithContext;
|
||||
auth?: Auth;
|
||||
};
|
||||
|
@ -5,9 +5,10 @@ export class Task {
|
||||
fileName: string;
|
||||
totalSize: number;
|
||||
completedSize: number;
|
||||
status: 'queuing' | 'in-progress' | 'completed';
|
||||
status: 'queuing' | 'in-progress' | 'completed' | 'failed';
|
||||
type: 'download' | 'upload';
|
||||
beginTime: number;
|
||||
errors: Error[] = [];
|
||||
|
||||
constructor(
|
||||
fileName: string,
|
||||
@ -26,10 +27,14 @@ export class Task {
|
||||
this.status = 'in-progress';
|
||||
}
|
||||
|
||||
finish() {
|
||||
complete() {
|
||||
this.status = 'completed';
|
||||
}
|
||||
|
||||
setErrors(errors: Error[]) {
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
reportProgress(size: number) {
|
||||
this.completedSize = size;
|
||||
}
|
||||
|
0
src/server/manager/utils.ts
Normal file
0
src/server/manager/utils.ts
Normal file
32
src/server/manager/utils/logger.ts
Normal file
32
src/server/manager/utils/logger.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Context } from 'src/server/manager/model/context';
|
||||
import { Logger as BaseLogger } from 'src/utils/logger';
|
||||
|
||||
export class Logger extends BaseLogger {
|
||||
static override prefix() {
|
||||
return '[Manager]';
|
||||
}
|
||||
|
||||
static ctx(ctx: Context) {
|
||||
return new LoggerWithContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
export class LoggerWithContext {
|
||||
constructor(private readonly ctx: Context) {}
|
||||
|
||||
fmtCtx() {
|
||||
return `[${this.ctx.id}] [${this.ctx.user ?? '-'}]`;
|
||||
}
|
||||
|
||||
info(...args: any[]) {
|
||||
Logger.info(this.fmtCtx(), ...args);
|
||||
}
|
||||
|
||||
error(err: string | Error) {
|
||||
console.error(
|
||||
`[${Logger.getTime()}] [ERROR] ${Logger.prefix()} ${this.fmtCtx()} ${Logger.errorMsg(
|
||||
err,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
@ -3,39 +3,41 @@ import { AggregatedError, BusinessError, TechnicalError } from '../errors/base';
|
||||
export class Logger {
|
||||
static tzOffset = new Date().getTimezoneOffset() * 60000;
|
||||
|
||||
static prefix() {
|
||||
return '[TGFS]';
|
||||
}
|
||||
|
||||
static getTime() {
|
||||
return new Date(Date.now() - this.tzOffset).toISOString().slice(0, -1);
|
||||
}
|
||||
|
||||
static debug(...args: any[]) {
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.debug(`[${this.getTime()}] [DEBUG]`, ...args);
|
||||
console.debug(`[${this.getTime()}] [DEBUG] ${this.prefix()}`, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
static info(...args: any[]) {
|
||||
console.info(`[${this.getTime()}] [INFO]`, ...args);
|
||||
console.info(`[${this.getTime()}] [INFO] ${this.prefix()}`, ...args);
|
||||
}
|
||||
|
||||
static errorMsg(err: string | Error) {
|
||||
if (err instanceof AggregatedError) {
|
||||
err.errors.forEach((e) => this.errorMsg(e));
|
||||
} else if (err instanceof BusinessError) {
|
||||
return `${err.code} (message: ${err.message} \n${err.stack}) (cause: ${err.cause})`;
|
||||
} else if (err instanceof TechnicalError) {
|
||||
return `Technical Error: (message: ${err.message}) (cause: ${err.cause})\n${err.stack})`;
|
||||
} else if (err instanceof Error) {
|
||||
return `${err.name} ${err.message}\n${err.stack}`;
|
||||
} else {
|
||||
return `${this.prefix()} ${err}`;
|
||||
}
|
||||
}
|
||||
|
||||
static error(err: string | Error) {
|
||||
if (err instanceof AggregatedError) {
|
||||
err.errors.forEach((e) => this.error(e));
|
||||
} else if (err instanceof BusinessError) {
|
||||
console.error(
|
||||
`[${this.getTime()}] [ERROR] ${err.code} ${err.name} ${err.message} \n${err.stack}`,
|
||||
);
|
||||
} else if (err instanceof TechnicalError) {
|
||||
console.error(
|
||||
`[${this.getTime()}] [ERROR] ${err.name} ${err.message} ${err.cause}\n${
|
||||
err.stack
|
||||
}`,
|
||||
);
|
||||
} else if (err instanceof Error) {
|
||||
console.error(
|
||||
`[${this.getTime()}] [ERROR] ${err.name} ${err.message}\n${err.stack}`,
|
||||
);
|
||||
} else {
|
||||
console.error(`[${this.getTime()}] [ERROR] ${err}`);
|
||||
}
|
||||
console.error(
|
||||
`[${this.getTime()}] [ERROR] ${this.prefix()} ${this.errorMsg(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ describe('config', () => {
|
||||
jwt: {
|
||||
secret: 'mock-secret',
|
||||
algorithm: 'HS256',
|
||||
expiration: 3600 * 24 * 7,
|
||||
life: 3600 * 24 * 7,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
92
yarn.lock
92
yarn.lock
@ -1080,11 +1080,6 @@ base64-js@^1.3.1:
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
|
||||
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
|
||||
|
||||
base64-url@^2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/base64-url/-/base64-url-2.3.3.tgz#645b71455c75109511f27d98450327e455f488ec"
|
||||
integrity sha512-dLMhIsK7OplcDauDH/tZLvK7JmUZK3A7KiQpjNzsBrM6Etw7hzNI1tLEywqJk9NnwkgWuFKSlx/IUO7vF6Mo8Q==
|
||||
|
||||
big-integer@^1.6.48:
|
||||
version "1.6.52"
|
||||
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85"
|
||||
@ -1165,6 +1160,11 @@ buffer-alloc@^1.2.0:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-equal-constant-time@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
|
||||
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
@ -2184,11 +2184,6 @@ is-typedarray@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
|
||||
|
||||
is.object@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is.object/-/is.object-1.0.0.tgz#e4f4117e9f083b35c8df5cf817ea3efb0452fdfa"
|
||||
integrity sha512-BdDP6tLXkf0nrCnksLobALJxkt2hmrVL6ge1oRuzGU4Lb9NpreEbhhuCcY6HMzx/qo3Dff9DJ3jf0x9+U0bNMQ==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
@ -2665,6 +2660,39 @@ json5@^2.2.3:
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jsonwebtoken@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3"
|
||||
integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==
|
||||
dependencies:
|
||||
jws "^3.2.2"
|
||||
lodash.includes "^4.3.0"
|
||||
lodash.isboolean "^3.0.3"
|
||||
lodash.isinteger "^4.0.4"
|
||||
lodash.isnumber "^3.0.3"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.once "^4.0.0"
|
||||
ms "^2.1.1"
|
||||
semver "^7.5.4"
|
||||
|
||||
jwa@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
|
||||
integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==
|
||||
dependencies:
|
||||
buffer-equal-constant-time "1.0.1"
|
||||
ecdsa-sig-formatter "1.0.11"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
jws@^3.2.2:
|
||||
version "3.2.2"
|
||||
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
|
||||
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
|
||||
dependencies:
|
||||
jwa "^1.4.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
kleur@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
@ -2687,11 +2715,46 @@ locate-path@^5.0.0:
|
||||
dependencies:
|
||||
p-locate "^4.1.0"
|
||||
|
||||
lodash.includes@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
|
||||
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
|
||||
|
||||
lodash.isboolean@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
|
||||
integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
|
||||
|
||||
lodash.isinteger@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
|
||||
integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
|
||||
|
||||
lodash.isnumber@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc"
|
||||
integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
|
||||
|
||||
lodash.memoize@4.x:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
|
||||
|
||||
lodash.once@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
|
||||
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
|
||||
|
||||
lodash@^4.17.21, lodash@^4.3.0, lodash@^4.6.1:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
@ -2817,7 +2880,7 @@ ms@2.1.2:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
|
||||
|
||||
ms@2.1.3:
|
||||
ms@2.1.3, ms@^2.1.1:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
|
||||
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||
@ -3212,7 +3275,7 @@ rx-lite@^3.1.2:
|
||||
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102"
|
||||
integrity sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ==
|
||||
|
||||
safe-buffer@5.2.1:
|
||||
safe-buffer@5.2.1, safe-buffer@^5.0.1:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
|
||||
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
|
||||
@ -3821,11 +3884,6 @@ xml-js@^1.6.2:
|
||||
dependencies:
|
||||
sax "^1.2.4"
|
||||
|
||||
xtend@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
|
Loading…
Reference in New Issue
Block a user