Fix backend typescript issues

This commit is contained in:
Louis Lam 2023-11-18 15:43:53 +08:00
parent 2cca168d39
commit f9761daefc
17 changed files with 144 additions and 105 deletions

View file

@ -5,6 +5,7 @@ import fs from "fs";
import path from "path"; import path from "path";
import knex from "knex"; import knex from "knex";
// @ts-ignore
import Dialect from "knex/lib/dialects/sqlite3/index.js"; import Dialect from "knex/lib/dialects/sqlite3/index.js";
import sqlite from "@louislam/sqlite3"; import sqlite from "@louislam/sqlite3";
@ -12,6 +13,11 @@ import { sleep } from "./util-common";
interface DBConfig { interface DBConfig {
type?: "sqlite" | "mysql"; type?: "sqlite" | "mysql";
hostname?: string;
port?: string;
database?: string;
username?: string;
password?: string;
} }
export class Database { export class Database {
@ -19,7 +25,7 @@ export class Database {
* SQLite file path (Default: ./data/dockge.db) * SQLite file path (Default: ./data/dockge.db)
* @type {string} * @type {string}
*/ */
static sqlitePath; static sqlitePath : string;
static noReject = true; static noReject = true;
@ -51,7 +57,7 @@ export class Database {
* @typedef {string|undefined} envString * @typedef {string|undefined} envString
* @returns {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString}} Database config * @returns {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString}} Database config
*/ */
static readDBConfig() { static readDBConfig() : DBConfig {
const dbConfigString = fs.readFileSync(path.join(this.server.config.dataDir, "db-config.json")).toString("utf-8"); const dbConfigString = fs.readFileSync(path.join(this.server.config.dataDir, "db-config.json")).toString("utf-8");
const dbConfig = JSON.parse(dbConfigString); const dbConfig = JSON.parse(dbConfigString);
@ -67,10 +73,10 @@ export class Database {
/** /**
* @typedef {string|undefined} envString * @typedef {string|undefined} envString
* @param {{type: "sqlite"} | {type:envString, hostname:envString, port:envString, database:envString, username:envString, password:envString}} dbConfig the database configuration that should be written * @param dbConfig the database configuration that should be written
* @returns {void} * @returns {void}
*/ */
static writeDBConfig(dbConfig) { static writeDBConfig(dbConfig : DBConfig) {
fs.writeFileSync(path.join(this.server.config.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4)); fs.writeFileSync(path.join(this.server.config.dataDir, "db-config.json"), JSON.stringify(dbConfig, null, 4));
} }
@ -82,12 +88,15 @@ export class Database {
*/ */
static async connect(autoloadModels = true) { static async connect(autoloadModels = true) {
const acquireConnectionTimeout = 120 * 1000; const acquireConnectionTimeout = 120 * 1000;
let dbConfig; let dbConfig : DBConfig;
try { try {
dbConfig = this.readDBConfig(); dbConfig = this.readDBConfig();
Database.dbConfig = dbConfig; Database.dbConfig = dbConfig;
} catch (err) { } catch (err) {
if (err instanceof Error) {
log.warn("db", err.message); log.warn("db", err.message);
}
dbConfig = { dbConfig = {
type: "sqlite", type: "sqlite",
}; };
@ -176,6 +185,7 @@ export class Database {
directory: Database.knexMigrationsPath, directory: Database.knexMigrationsPath,
}); });
} catch (e) { } catch (e) {
if (e instanceof Error) {
// Allow missing patch files for downgrade or testing pr. // Allow missing patch files for downgrade or testing pr.
if (e.message.includes("the following files are missing:")) { if (e.message.includes("the following files are missing:")) {
log.warn("db", e.message); log.warn("db", e.message);
@ -186,6 +196,7 @@ export class Database {
} }
} }
} }
}
/** /**
* Special handle, because tarn.js throw a promise reject that cannot be caught * Special handle, because tarn.js throw a promise reject that cannot be caught

View file

@ -60,7 +60,7 @@ export class DockgeServer {
*/ */
needSetup = false; needSetup = false;
jwtSecret? : string; jwtSecret : string = "";
stacksDir : string = ""; stacksDir : string = "";
@ -129,7 +129,7 @@ export class DockgeServer {
this.config.sslKey = args.sslKey || process.env.DOCKGE_SSL_KEY || undefined; this.config.sslKey = args.sslKey || process.env.DOCKGE_SSL_KEY || undefined;
this.config.sslCert = args.sslCert || process.env.DOCKGE_SSL_CERT || undefined; this.config.sslCert = args.sslCert || process.env.DOCKGE_SSL_CERT || undefined;
this.config.sslKeyPassphrase = args.sslKeyPassphrase || process.env.DOCKGE_SSL_KEY_PASSPHRASE || undefined; this.config.sslKeyPassphrase = args.sslKeyPassphrase || process.env.DOCKGE_SSL_KEY_PASSPHRASE || undefined;
this.config.port = args.port || parseInt(process.env.DOCKGE_PORT) || 5001; this.config.port = args.port || Number(process.env.DOCKGE_PORT) || 5001;
this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined; this.config.hostname = args.hostname || process.env.DOCKGE_HOSTNAME || undefined;
this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/"; this.config.dataDir = args.dataDir || process.env.DOCKGE_DATA_DIR || "./data/";
this.config.stacksDir = args.stacksDir || process.env.DOCKGE_STACKS_DIR || defaultStacksDir; this.config.stacksDir = args.stacksDir || process.env.DOCKGE_STACKS_DIR || defaultStacksDir;
@ -218,7 +218,7 @@ export class DockgeServer {
log.debug("auth", "check auto login"); log.debug("auth", "check auto login");
if (await Settings.get("disableAuth")) { if (await Settings.get("disableAuth")) {
log.info("auth", "Disabled Auth: auto login to admin"); log.info("auth", "Disabled Auth: auto login to admin");
this.afterLogin(socket as DockgeSocket, await R.findOne("user")); this.afterLogin(socket as DockgeSocket, await R.findOne("user") as User);
socket.emit("autoLogin"); socket.emit("autoLogin");
} else { } else {
log.debug("auth", "need auth"); log.debug("auth", "need auth");
@ -253,7 +253,9 @@ export class DockgeServer {
try { try {
await Database.init(this); await Database.init(this);
} catch (e) { } catch (e) {
if (e instanceof Error) {
log.error("server", "Failed to prepare your database: " + e.message); log.error("server", "Failed to prepare your database: " + e.message);
}
process.exit(1); process.exit(1);
} }
@ -376,8 +378,10 @@ export class DockgeServer {
return process.env.TZ; return process.env.TZ;
} }
} catch (e) { } catch (e) {
if (e instanceof Error) {
log.warn("timezone", e.message + " in process.env.TZ"); log.warn("timezone", e.message + " in process.env.TZ");
} }
}
const timezone = await Settings.get("serverTimezone"); const timezone = await Settings.get("serverTimezone");
@ -389,8 +393,10 @@ export class DockgeServer {
return timezone; return timezone;
} }
} catch (e) { } catch (e) {
if (e instanceof Error) {
log.warn("timezone", e.message + " in settings"); log.warn("timezone", e.message + " in settings");
} }
}
// Guess // Guess
try { try {

View file

@ -17,7 +17,7 @@ export function generatePasswordHash(password : string) {
* @param {string} hash Hash to verify against * @param {string} hash Hash to verify against
* @returns {boolean} Does the password match the hash? * @returns {boolean} Does the password match the hash?
*/ */
export function verifyPassword(password, hash) { export function verifyPassword(password : string, hash : string) {
return bcrypt.compareSync(password, hash); return bcrypt.compareSync(password, hash);
} }
@ -37,7 +37,7 @@ export const SHAKE256_LENGTH = 16;
* @param {number} len Output length of the hash * @param {number} len Output length of the hash
* @returns {string} The hashed data in hex format * @returns {string} The hashed data in hex format
*/ */
export function shake256(data, len) { export function shake256(data : string, len : number) {
if (!data) { if (!data) {
return ""; return "";
} }

View file

@ -1,8 +1,14 @@
// "limit" is bugged in Typescript, use "limiter-es6-compat" instead // "limit" is bugged in Typescript, use "limiter-es6-compat" instead
// See https://github.com/jhurliman/node-rate-limiter/issues/80 // See https://github.com/jhurliman/node-rate-limiter/issues/80
import { RateLimiter } from "limiter-es6-compat"; import { RateLimiter, RateLimiterOpts } from "limiter-es6-compat";
import { log } from "./log"; import { log } from "./log";
export interface KumaRateLimiterOpts extends RateLimiterOpts {
errorMessage : string;
}
export type KumaRateLimiterCallback = (err : object) => void;
class KumaRateLimiter { class KumaRateLimiter {
errorMessage : string; errorMessage : string;
@ -11,7 +17,7 @@ class KumaRateLimiter {
/** /**
* @param {object} config Rate limiter configuration object * @param {object} config Rate limiter configuration object
*/ */
constructor(config) { constructor(config : KumaRateLimiterOpts) {
this.errorMessage = config.errorMessage; this.errorMessage = config.errorMessage;
this.rateLimiter = new RateLimiter(config); this.rateLimiter = new RateLimiter(config);
} }
@ -24,11 +30,11 @@ class KumaRateLimiter {
/** /**
* Should the request be passed through * Should the request be passed through
* @param {passCB} callback Callback function to call with decision * @param callback Callback function to call with decision
* @param {number} num Number of tokens to remove * @param {number} num Number of tokens to remove
* @returns {Promise<boolean>} Should the request be allowed? * @returns {Promise<boolean>} Should the request be allowed?
*/ */
async pass(callback, num = 1) { async pass(callback : KumaRateLimiterCallback, num = 1) {
const remainingRequests = await this.removeTokens(num); const remainingRequests = await this.removeTokens(num);
log.info("rate-limit", "remaining requests: " + remainingRequests); log.info("rate-limit", "remaining requests: " + remainingRequests);
if (remainingRequests < 0) { if (remainingRequests < 0) {

View file

@ -1,4 +1,4 @@
import { DockgeServer } from "../dockgeServer"; import { DockgeServer } from "../dockge-server";
import { Router } from "../router"; import { Router } from "../router";
import express, { Express, Router as ExpressRouter } from "express"; import express, { Express, Router as ExpressRouter } from "express";

View file

@ -1,5 +1,6 @@
import { R } from "redbean-node"; import { R } from "redbean-node";
import { log } from "./log"; import { log } from "./log";
import { LooseObject } from "./util-common";
export class Settings { export class Settings {
@ -15,20 +16,19 @@ export class Settings {
* timestamp: 12345678 * timestamp: 12345678
* }, * },
* } * }
* @type {{}}
*/ */
static cacheList = { static cacheList : LooseObject = {
}; };
static cacheCleaner = null; static cacheCleaner? : NodeJS.Timeout;
/** /**
* Retrieve value of setting based on key * Retrieve value of setting based on key
* @param {string} key Key of setting to retrieve * @param key Key of setting to retrieve
* @returns {Promise<any>} Value * @returns Value
*/ */
static async get(key) { static async get(key : string) {
// Start cache clear if not started yet // Start cache clear if not started yet
if (!Settings.cacheCleaner) { if (!Settings.cacheCleaner) {
@ -72,12 +72,12 @@ export class Settings {
/** /**
* Sets the specified setting to specified value * Sets the specified setting to specified value
* @param {string} key Key of setting to set * @param key Key of setting to set
* @param {any} value Value to set to * @param value Value to set to
* @param {?string} type Type of setting * @param {?string} type Type of setting
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async set(key, value, type = null) { static async set(key : string, value : object | string | number | boolean, type : string | null = null) {
let bean = await R.findOne("setting", " `key` = ? ", [ let bean = await R.findOne("setting", " `key` = ? ", [
key, key,
@ -95,15 +95,15 @@ export class Settings {
/** /**
* Get settings based on type * Get settings based on type
* @param {string} type The type of setting * @param type The type of setting
* @returns {Promise<Bean>} Settings * @returns Settings
*/ */
static async getSettings(type) { static async getSettings(type : string) {
const list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [ const list = await R.getAll("SELECT `key`, `value` FROM setting WHERE `type` = ? ", [
type, type,
]); ]);
const result = {}; const result : LooseObject = {};
for (const row of list) { for (const row of list) {
try { try {
@ -118,11 +118,11 @@ export class Settings {
/** /**
* Set settings based on type * Set settings based on type
* @param {string} type Type of settings to set * @param type Type of settings to set
* @param {object} data Values of settings * @param data Values of settings
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
static async setSettings(type, data) { static async setSettings(type : string, data : LooseObject) {
const keyList = Object.keys(data); const keyList = Object.keys(data);
const promiseList = []; const promiseList = [];
@ -154,7 +154,7 @@ export class Settings {
* @param {string[]} keyList Keys to remove * @param {string[]} keyList Keys to remove
* @returns {void} * @returns {void}
*/ */
static deleteCache(keyList) { static deleteCache(keyList : string[]) {
for (const key of keyList) { for (const key of keyList) {
delete Settings.cacheList[key]; delete Settings.cacheList[key];
} }
@ -167,7 +167,7 @@ export class Settings {
static stopCacheCleaner() { static stopCacheCleaner() {
if (Settings.cacheCleaner) { if (Settings.cacheCleaner) {
clearInterval(Settings.cacheCleaner); clearInterval(Settings.cacheCleaner);
Settings.cacheCleaner = null; Settings.cacheCleaner = undefined;
} }
} }
} }

View file

@ -5,7 +5,7 @@ import { R } from "redbean-node";
import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter"; import { loginRateLimiter, twoFaRateLimiter } from "../rate-limiter";
import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash"; import { generatePasswordHash, needRehashPassword, shake256, SHAKE256_LENGTH, verifyPassword } from "../password-hash";
import { User } from "../models/user"; import { User } from "../models/user";
import { checkLogin, DockgeSocket, doubleCheckPassword } from "../util-server"; import { checkLogin, DockgeSocket, doubleCheckPassword, JWTDecoded } from "../util-server";
import { passwordStrength } from "check-password-strength"; import { passwordStrength } from "check-password-strength";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { Settings } from "../settings"; import { Settings } from "../settings";
@ -42,11 +42,13 @@ export class MainSocketHandler extends SocketHandler {
}); });
} catch (e) { } catch (e) {
if (e instanceof Error) {
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
}); });
} }
}
}); });
// Login by token // Login by token
@ -56,7 +58,7 @@ export class MainSocketHandler extends SocketHandler {
log.info("auth", `Login by token. IP=${clientIP}`); log.info("auth", `Login by token. IP=${clientIP}`);
try { try {
const decoded = jwt.verify(token, server.jwtSecret); const decoded = jwt.verify(token, server.jwtSecret) as JWTDecoded;
log.info("auth", "Username from JWT: " + decoded.username); log.info("auth", "Username from JWT: " + decoded.username);
@ -90,9 +92,13 @@ export class MainSocketHandler extends SocketHandler {
}); });
} }
} catch (error) { } catch (error) {
if (!(error instanceof Error)) {
console.error("Unknown error:", error);
return;
}
log.error("auth", `Invalid token. IP=${clientIP}`); log.error("auth", `Invalid token. IP=${clientIP}`);
if (error.message) { if (error.message) {
log.error("auth", error.message, `IP=${clientIP}`); log.error("auth", error.message + ` IP=${clientIP}`);
} }
callback({ callback({
ok: false, ok: false,
@ -148,6 +154,7 @@ export class MainSocketHandler extends SocketHandler {
} }
if (data.token) { if (data.token) {
// @ts-ignore
const verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions); const verify = notp.totp.verify(data.token, user.twofa_secret, twoFAVerifyOptions);
if (user.twofa_last_token !== data.token && verify) { if (user.twofa_last_token !== data.token && verify) {
@ -210,11 +217,13 @@ export class MainSocketHandler extends SocketHandler {
}); });
} catch (e) { } catch (e) {
if (e instanceof Error) {
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
}); });
} }
}
}); });
socket.on("getSettings", async (callback) => { socket.on("getSettings", async (callback) => {
@ -228,11 +237,13 @@ export class MainSocketHandler extends SocketHandler {
}); });
} catch (e) { } catch (e) {
if (e instanceof Error) {
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
}); });
} }
}
}); });
socket.on("setSettings", async (data, currentPassword, callback) => { socket.on("setSettings", async (data, currentPassword, callback) => {
@ -261,22 +272,24 @@ export class MainSocketHandler extends SocketHandler {
server.sendInfo(socket); server.sendInfo(socket);
} catch (e) { } catch (e) {
if (e instanceof Error) {
callback({ callback({
ok: false, ok: false,
msg: e.message, msg: e.message,
}); });
} }
}
}); });
} }
async login(username : string, password : string) { async login(username : string, password : string) : Promise<User | null> {
if (typeof username !== "string" || typeof password !== "string") { if (typeof username !== "string" || typeof password !== "string") {
return null; return null;
} }
const user = await R.findOne("user", " username = ? AND active = 1 ", [ const user = await R.findOne("user", " username = ? AND active = 1 ", [
username, username,
]); ]) as User;
if (user && verifyPassword(password, user.password)) { if (user && verifyPassword(password, user.password)) {
// Upgrade the hash to bcrypt // Upgrade the hash to bcrypt

View file

@ -38,11 +38,13 @@ export class TerminalSocketHandler extends SocketHandler {
throw new Error("Terminal not found or it is not a Interactive Terminal."); throw new Error("Terminal not found or it is not a Interactive Terminal.");
} }
} catch (e) { } catch (e) {
if (e instanceof Error) {
errorCallback({ errorCallback({
ok: false, ok: false,
msg: e.message, msg: e.message,
}); });
} }
}
}); });
// Main Terminal // Main Terminal

View file

@ -197,9 +197,11 @@ export class Stack {
stack._status = CREATED_FILE; stack._status = CREATED_FILE;
stackList.set(filename, stack); stackList.set(filename, stack);
} catch (e) { } catch (e) {
if (e instanceof Error) {
log.warn("getStackList", `Failed to get stack ${filename}, error: ${e.message}`); log.warn("getStackList", `Failed to get stack ${filename}, error: ${e.message}`);
} }
} }
}
// Cache by copying // Cache by copying
this.managedStackList = new Map(stackList); this.managedStackList = new Map(stackList);

View file

@ -54,9 +54,11 @@ export class Terminal {
try { try {
this.ptyProcess?.resize(this.cols, this.rows); this.ptyProcess?.resize(this.cols, this.rows);
} catch (e) { } catch (e) {
if (e instanceof Error) {
log.debug("Terminal", "Failed to resize terminal: " + e.message); log.debug("Terminal", "Failed to resize terminal: " + e.message);
} }
} }
}
get cols() { get cols() {
return this._cols; return this._cols;
@ -67,9 +69,11 @@ export class Terminal {
try { try {
this.ptyProcess?.resize(this.cols, this.rows); this.ptyProcess?.resize(this.cols, this.rows);
} catch (e) { } catch (e) {
if (e instanceof Error) {
log.debug("Terminal", "Failed to resize terminal: " + e.message); log.debug("Terminal", "Failed to resize terminal: " + e.message);
} }
} }
}
public start() { public start() {
if (this._ptyProcess) { if (this._ptyProcess) {
@ -85,7 +89,7 @@ export class Terminal {
// On Data // On Data
this._ptyProcess.onData((data) => { this._ptyProcess.onData((data) => {
this.buffer.push(data); this.buffer.pushItem(data);
if (this.server.io) { if (this.server.io) {
this.server.io.to(this.name).emit("terminalWrite", this.name, data); this.server.io.to(this.name).emit("terminalWrite", this.name, data);
} }

View file

@ -12,6 +12,11 @@ dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
export interface LooseObject {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any
}
let randomBytes : (numBytes: number) => Uint8Array; let randomBytes : (numBytes: number) => Uint8Array;
initRandomBytes(); initRandomBytes();

View file

@ -6,6 +6,11 @@ import { ERROR_TYPE_VALIDATION } from "./util-common";
import { R } from "redbean-node"; import { R } from "redbean-node";
import { verifyPassword } from "./password-hash"; import { verifyPassword } from "./password-hash";
export interface JWTDecoded {
username : string;
h? : string;
}
export interface DockgeSocket extends Socket { export interface DockgeSocket extends Socket {
userID: number; userID: number;
consoleTerminal? : Terminal; consoleTerminal? : Terminal;

View file

@ -4,14 +4,14 @@
*/ */
export class LimitQueue<T> extends Array<T> { export class LimitQueue<T> extends Array<T> {
__limit; __limit;
__onExceed = null; __onExceed? : (item : T | undefined) => void;
constructor(limit: number) { constructor(limit: number) {
super(); super();
this.__limit = limit; this.__limit = limit;
} }
push(value : T) { pushItem(value : T) {
super.push(value); super.push(value);
if (this.length > this.__limit) { if (this.length > this.__limit) {
const item = this.shift(); const item = this.shift();

View file

@ -10,7 +10,7 @@ import { POSITION } from "vue-toastification";
* *
* Generated by Trelent * Generated by Trelent
*/ */
function getTimezoneOffset(timeZone) { function getTimezoneOffset(timeZone : string) {
const now = new Date(); const now = new Date();
const tzString = now.toLocaleString("en-US", { const tzString = now.toLocaleString("en-US", {
timeZone, timeZone,
@ -124,33 +124,6 @@ export function hostNameRegexPattern(mqtt = false) {
return `${ipRegexPattern}|${hostNameRegexPattern}`; return `${ipRegexPattern}|${hostNameRegexPattern}`;
} }
/**
* Get the tag color options
* Shared between components
* @param {any} self Component
* @returns {object[]} Colour options
*/
export function colorOptions(self) {
return [
{ name: self.$t("Gray"),
color: "#4B5563" },
{ name: self.$t("Red"),
color: "#DC2626" },
{ name: self.$t("Orange"),
color: "#D97706" },
{ name: self.$t("Green"),
color: "#059669" },
{ name: self.$t("Blue"),
color: "#2563EB" },
{ name: self.$t("Indigo"),
color: "#4F46E5" },
{ name: self.$t("Purple"),
color: "#7C3AED" },
{ name: self.$t("Pink"),
color: "#DB2777" },
];
}
/** /**
* Loads the toast timeout settings from storage. * Loads the toast timeout settings from storage.
* @returns {object} The toast plugin options object. * @returns {object} The toast plugin options object.

View file

@ -50,6 +50,7 @@
"@fortawesome/free-regular-svg-icons": "6.4.2", "@fortawesome/free-regular-svg-icons": "6.4.2",
"@fortawesome/free-solid-svg-icons": "6.4.2", "@fortawesome/free-solid-svg-icons": "6.4.2",
"@fortawesome/vue-fontawesome": "3.0.3", "@fortawesome/vue-fontawesome": "3.0.3",
"@types/bcryptjs": "^2.4.6",
"@types/bootstrap": "~5.2.9", "@types/bootstrap": "~5.2.9",
"@types/command-exists": "~1.2.3", "@types/command-exists": "~1.2.3",
"@types/express": "~4.17.21", "@types/express": "~4.17.21",

View file

@ -97,6 +97,9 @@ devDependencies:
'@fortawesome/vue-fontawesome': '@fortawesome/vue-fontawesome':
specifier: 3.0.3 specifier: 3.0.3
version: 3.0.3(@fortawesome/fontawesome-svg-core@6.4.2)(vue@3.3.8) version: 3.0.3(@fortawesome/fontawesome-svg-core@6.4.2)(vue@3.3.8)
'@types/bcryptjs':
specifier: ^2.4.6
version: 2.4.6
'@types/bootstrap': '@types/bootstrap':
specifier: ~5.2.9 specifier: ~5.2.9
version: 5.2.9 version: 5.2.9
@ -1013,6 +1016,10 @@ packages:
dev: false dev: false
optional: true optional: true
/@types/bcryptjs@2.4.6:
resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==}
dev: true
/@types/body-parser@1.19.5: /@types/body-parser@1.19.5:
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
dependencies: dependencies:

View file

@ -3,6 +3,10 @@
"module": "ESNext", "module": "ESNext",
"target": "ESNext", "target": "ESNext",
"strict": true, "strict": true,
"moduleResolution": "bundler" "moduleResolution": "bundler",
} "skipLibCheck": true
},
"include": [
"backend/**/*"
],
} }